To run the project locally, follow these steps:
-
Create a copy of the
.env.examplefile to.env.localand configure the necessary environment variables. -
Install dependencies with
bun install. -
To start the server:
- Use
bun run devto run in development mode. - Or
bun run dev:servicesif you want to run with the database via Docker.
- Use
-
To shut down the Docker services, use
bun services:down.
This is a backend API for generating content, built with Express.js, TypeScript, and MongoDB. It includes user authentication, JWT handling, and dynamic routing.
To make services accessible via req.services (e.g., req.services.UserService) in your Express routes, you need to extend the Express Request interface and attach the services through a middleware. This approach ensures type safety and easy access to shared services across the application.
In src/types/Global.d.ts, extend the Express Request interface to include the services property. This provides TypeScript typings for autocompletion and error checking.
//src/types/Global.d.ts
declare global {
namespace Express {
interface Request {
services: {
UserService: UserService;
JwtService: JwtService;
// Add more services here as needed
};
}
}
}In src/index.ts, add a middleware that instantiates and attaches the services to every incoming request. This is done early in the middleware chain.
//src/index.ts
server.use((req, res, next) => {
req.services = {
UserService: new UserService(),
JwtService: new JwtService(),
// Add more services here
};
next();
});Once set up, you can access services in any route handler:
router.get('/example', (req, res) => {
const userService = req.services.UserService;
// Use the service
});This method promotes dependency injection and keeps your code modular and testable.
The API uses a custom RouterService to handle dynamic route registration. This service scans directories for route files and registers them automatically, making it easy to add new endpoints without modifying the main server file.
In src/index.ts, the RouterService is initialized and mounted to the Express app. It scans the specified base directory for route files.
//src/index.ts
server.use(
RouterService.Routes({
baseDir: 'src/routes/api',
routePath: '/api',
}),
);- baseDir: The directory to scan for route files.
- routePath: The base path for all registered routes (e.g., '/api').
Routes are defined in files named route.ts within the src/routes/api directory structure. The folder structure determines the URL path.
For a static route like /api/users, create src/routes/api/users/route.ts.
For dynamic parameters, use brackets in folder names. For example, to create a route /api/users/:userId, use a folder named [userId] containing route.ts:
- Folder:
src/routes/api/users/[userId] - File:
src/routes/api/users/[userId]/route.ts
Inside route.ts, export a DynamicRoute object defining HTTP methods:
//src/routes/api/users/[userId]/route.ts
import type { DynamicRoute } from '@/services/routerService';
const route: DynamicRoute = {
GET: (req, res) => {
const userId = req.params.userId;
res.json({ userId });
},
};
export default route;The RouterService will automatically register this as GET /api/users/:userId.
Here are different ways to configure routes using the DynamicRoute interface and Route type:
A basic GET handler without middlewares.
// route.ts
import type { DynamicRoute } from '@/services/routerService';
const route: DynamicRoute = {
GET: (req, res) => {
res.json({ message: 'Hello World' });
},
};
export default route;Use an object for a method to add specific middlewares.
// route.ts
import type { DynamicRoute } from '@/services/routerService';
import { authMiddleware } from '@/middlewares/auth';
const route: DynamicRoute = {
POST: {
middlewares: [authMiddleware],
handler: (req, res) => {
res.json({ message: 'Protected POST' });
},
},
};
export default route;Apply middlewares to all methods in the route.
// route.ts
import type { DynamicRoute } from '@/services/routerService';
import { authMiddleware } from '@/middlewares/auth';
const route: DynamicRoute = {
middlewares: [authMiddleware],
GET: (req, res) => {
res.json({ user: req.user });
},
DELETE: (req, res) => {
res.json({ message: 'Deleted' });
},
};
export default route;Combine simple handlers and object-based configurations with multiple methods.
// route.ts
import type { DynamicRoute } from '@/services/routerService';
import { authMiddleware, logMiddleware } from '@/middlewares';
const route: DynamicRoute = {
middlewares: [logMiddleware],
GET: (req, res) => {
res.json({ data: 'GET response' });
},
PUT: {
middlewares: [authMiddleware],
handler: (req, res) => {
res.json({ message: 'Updated' });
},
},
};
export default route;These examples show the flexibility of the routing system.
With the recent update, you can now export middlewares and HTTP methods separately without using a default export. This allows for more modular route definitions.
// route.ts
import type { Request, Response } from 'express';
import { isAuthenticated } from '@/middlewares/auth';
export const middlewares = [isAuthenticated];
export const GET = async (req: Request, res: Response) => {
try {
return res.status(200).json(req.user);
}
catch (err) {
req.logger.error(err);
res.status(500).json({ message: 'Internal server error' });
}
}This approach enables exporting components individually, which the RouterService will compose into a DynamicRoute.
- Middlewares: Add global or per-method middlewares in the route object.
- Supported Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD.
- Logging: Routes are logged upon registration.
This system allows scalable route management by simply adding files and folders.