A simple link-shortening service built with TypeScript, React (Vite), Mantine UI, and Express. It uses OIDC for authentication and JWT for API authorization. Enter a long URL, and Femto generates a shorter, more memorable link. This system is intended for internal use (e.g., linking forms, events, or other resources).
- Shorten URLs quickly and easily via a web interface.
- OIDC Authentication for user login.
- JWT Authorization for protected API endpoints.
- View/Manage created links (for authenticated users).
- View Link Statistics (click counts, timestamps).
- Automatic Slug Generation if none is provided.
- Custom Slug Support (if slug is available).
- Optional Expiry Dates for links.
- Optional Group Association for links.
- QR Code Generation for shortened links.
- Blacklist Management for blocked URLs or slugs.
- Database-Backed with PostgreSQL.
- Node.js v22 or higher (for local development).
- Docker (for containerized deployment).
- Docker Compose (to orchestrate multiple containers).
- Access to an OIDC Provider (like Keycloak, Auth0, or a custom one like
sso.datasektionen.se
) for authentication.
femto/
├── .github/
│ └── workflows/
│ └── deploy-dual.yml # GitHub Actions CI/CD configuration
├── client/ # Frontend React (Vite) application
│ ├── public/ # Static assets (icons, manifest)
│ ├── src/ # Source code
│ │ ├── authorization/ # Auth context, hooks, OIDC logic
│ │ │ ├── authApi.ts # Authentication API calls
│ │ │ ├── AuthContext.tsx # React context for auth state
│ │ │ ├── types.ts # Auth-related TypeScript types
│ │ │ └── useAuth.ts # Auth hook
│ │ ├── components/ # Reusable React components
│ │ │ ├── auth/ # Authentication components
│ │ │ │ ├── LoginRedirect.tsx
│ │ │ │ ├── Logout.tsx
│ │ │ │ ├── OIDCCallback.tsx
│ │ │ │ └── ProtectedRoute.tsx
│ │ │ └── LinkCreator.tsx # Main link creation component
│ │ ├── types/ # TypeScript type definitions
│ │ │ └── methone.d.ts # Methone-specific types
│ │ ├── views/ # Page components
│ │ │ ├── Blacklist.tsx # Blacklist management page
│ │ │ ├── Home.tsx # Main homepage
│ │ │ ├── LinkDetails.tsx # Individual link statistics
│ │ │ └── Links.tsx # User's links overview
│ │ ├── App.css # Application styles
│ │ ├── App.tsx # Main application component
│ │ ├── configuration.ts # Configuration constants
│ │ ├── index.css # Global styles
│ │ ├── index.tsx # Application entry point
│ │ └── vite-env.d.ts # Vite environment types
│ ├── .env # Environment variables (local)
│ ├── .env.example # Example environment variables
│ ├── .gitignore # Git ignore rules
│ ├── Dockerfile.client # Docker configuration for client
│ ├── eslint.config.js # ESLint configuration
│ ├── index.html # HTML template
│ ├── nginx.conf # Nginx configuration for Docker
│ ├── package.json # Dependencies and scripts
│ ├── tsconfig.app.json # TypeScript config for app
│ ├── tsconfig.json # Main TypeScript configuration
│ ├── tsconfig.node.json # TypeScript config for Node tools
│ └── vite.config.ts # Vite configuration
├── server/ # Backend Express application
│ ├── database/ # Database setup and schema
│ │ ├── insert.sql # Sample data insertion
│ │ └── schema.sql # Database schema definition
│ ├── src/ # Source code
│ │ ├── controllers/ # Request handlers
│ │ │ ├── authController.ts # Authentication logic
│ │ │ ├── blacklistController.ts # Blacklist management
│ │ │ ├── linkController.ts # Link CRUD operations
│ │ │ └── statusController.ts # Health check endpoints
│ │ ├── middlewares/ # Express middleware
│ │ │ └── jwtAuthMiddleware.ts # JWT token validation
│ │ ├── routes/ # Route definitions
│ │ │ ├── apiRouter.ts # API endpoints
│ │ │ ├── loginRouter.ts # Authentication routes
│ │ │ └── redirectRouter.ts # URL redirection logic
│ │ ├── services/ # Business logic services
│ │ │ ├── cleanupService.ts # Expired link cleanup
│ │ │ └── db.ts # Database connection and queries
│ │ └── index.ts # Server entry point
│ ├── .env # Environment variables (local)
│ ├── .env.example # Example environment variables
│ ├── .gitignore # Git ignore rules
│ ├── Dockerfile.server # Docker configuration for server
│ ├── package.json # Dependencies and scripts
│ └── tsconfig.json # TypeScript configuration
├── docker-compose.yml # Docker Compose configuration
├── job.nomad.hcl # Nomad job specification
└── README.md # This file
git clone https://github.com/datasektionen/femto.git
cd femto
This project requires separate environment variables for the client and server.
- Copy
server/.env.example
toserver/.env
and fill in the values. - Copy
client/.env.example
toclient/.env
and fill in the values.
The project includes SQL files for database initialization:
server/database/schema.sql
- Contains the database schemaserver/database/insert.sql
- Contains sample data (optional)
Schema setup is run automatically.
This is the easiest way to get all services (client, server, database) running together.
-
Ensure your
server/.env
file hasPOSTGRES_HOST=postgres
. -
Build and start the containers:
docker-compose up --build -d
-d
runs the containers in the background.
This will:
- Launch the client React app, accessible at http://localhost:3000/
- Launch the server Express app, accessible at http://localhost:5000/
- Launch a PostgreSQL database container, accessible to the server via the hostname
postgres
-
List running containers:
docker ps
-
Find the container ID for the
postgres
image. -
Connect using psql:
docker exec -it [container-ID] psql -U your_db_user -d your_db_name
You can also run the client and server directly on your machine. Make sure you have a separate PostgreSQL instance running and accessible.
- Create a PostgreSQL database
- Run the schema from
server/database/schema.sql
- Optionally run
server/database/insert.sql
for sample data
cd server
npm install
npm run dev # Runs server with auto-restart on changes
cd client
npm install
npm run dev # Starts development server
The client app should start on http://localhost:3000/.
- Login: Access the client URL (e.g., http://localhost:3000/). Click the login button, which will redirect you to your OIDC provider.
- Shorten a Link: Once logged in, use the form on the homepage to enter a long URL. You can optionally provide a custom slug, expiry date, or associate a mandate.
- View Links: Navigate to the "Länkar" (Links) page to see links you have created.
- View Stats: Click on a link to view its detailed statistics.
- Manage Blacklist: Access the blacklist page to manage blocked URLs or slugs.
- Redirect: Navigate to
<SERVER_URL>/<slug>
(e.g.,http://localhost:5000/myslug
) to be redirected to the original long URL.
-
Why is the client showing a blank page or login errors?
- Ensure both
client/.env
andserver/.env
files exist and are correctly configured. - Verify the
CLIENT_URL
inserver/.env
matches where your client is running. - Check the browser console and network tab for specific errors.
- Ensure the backend server is running and accessible at the specified
VITE_BACKEND_ROOT
.
- Ensure both
-
Database connection issues?
- Verify your PostgreSQL instance is running and accessible.
- Check that the database schema has been properly initialized.
- Ensure the connection parameters in
server/.env
are correct.