Implements REST controller layer for HTTP handling following Onion Architecture. Use when creating controllers, REST endpoints, or when the user asks to implement API endpoints for an entity.
Implements the REST controller (HTTP/presentation) layer following the project's Onion Architecture patterns.
mkdir -p src/rest/{entity-name}/controllers
mkdir -p src/rest/{entity-name}/dtos
mkdir -p src/rest/{entity-name}/mappers
Response DTO: src/rest/{entity-name}/dtos/{entity}.dto.ts
import { z } from 'zod';
export const EntityDtoSchema = z.object({
id: z.string().uuid(),
field: z.string(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
});
export type EntityDto = z.infer<typeof EntityDtoSchema>;
Create DTO: src/rest/{entity-name}/dtos/create-{entity}.dto.ts
import { z } from 'zod';
export const CreateEntityDtoSchema = z.object({
field: z.string().min(1),
});
export type CreateEntityDto = z.infer<typeof CreateEntityDtoSchema>;
Update DTO: src/rest/{entity-name}/dtos/update-{entity}.dto.ts
import { z } from 'zod';
export const UpdateEntityDtoSchema = z.object({
field: z.string().min(1).optional(),
});
export type UpdateEntityDto = z.infer<typeof UpdateEntityDtoSchema>;
File: src/rest/{entity-name}/mappers/{entity}.mapper.ts
import type { Entity } from '@/entities/{entity}/{entity}.js';
import type { EntityDto } from '../dtos/{entity}.dto.js';
export const toDto = (entity: Entity): EntityDto => {
return {
id: entity.id,
field: entity.field,
createdAt: entity.createdAt.toISOString(),
updatedAt: entity.updatedAt.toISOString(),
};
};
File: src/rest/{entity-name}/controllers/{entity}.controller.ts
import type { Request, Response } from 'express';
import type { Usecases } from '@/usecases/index.js';
import type { AppContext } from '@/libs/context/index.js';
import type { EntityDto } from '../dtos/{entity}.dto.js';
import type { CreateEntityDto } from '../dtos/create-{entity}.dto.js';
import type { UpdateEntityDto } from '../dtos/update-{entity}.dto.js';
import { toDto } from '../mappers/{entity}.mapper.js';
export const initEntityControllers = (usecases: Usecases) => {
const create = async (
req: Request<any, EntityDto, CreateEntityDto> & { context: AppContext },
res: Response<EntityDto>,
) => {
const entity = await usecases.entity.create(req.context, {
field: req.body.field,
});
const entityDto = toDto(entity);
res.status(201).json(entityDto);
};
const getAll = async (
req: Request<any, EntityDto[]> & { context: AppContext },
res: Response<EntityDto[]>,
) => {
const entities = await usecases.entity.getAll(req.context);
const entitiesDto = entities.map(toDto);
res.json(entitiesDto);
};
const getById = async (
req: Request<{ id: string }, EntityDto> & { context: AppContext },
res: Response<EntityDto>,
) => {
const entity = await usecases.entity.getById(req.context, {
id: req.params.id || '',
});
if (!entity) {
return res.status(404).json({ message: 'Entity not found' } as any);
}
const entityDto = toDto(entity);
res.json(entityDto);
};
const update = async (
req: Request<{ id: string }, EntityDto, UpdateEntityDto> & { context: AppContext },
res: Response<EntityDto>,
) => {
const entity = await usecases.entity.update(req.context, {
id: req.params.id || '',
field: req.body.field,
});
const entityDto = toDto(entity);
res.json(entityDto);
};
const deleteEntity = async (
req: Request<{ id: string }> & { context: AppContext },
res: Response<void>,
) => {
await usecases.entity.delete(req.context, {
id: req.params.id || '',
});
res.status(204).send();
};
return {
create,
getAll,
getById,
update,
delete: deleteEntity,
};
};
export type EntityControllers = ReturnType<typeof initEntityControllers>;
File: src/rest/{entity-name}/routes.ts
import { Router } from 'express';
import { initEntityControllers } from './controllers/{entity}.controller.js';
import type { Usecases } from '@/usecases/index.js';
export const initEntityRoutes = (usecases: Usecases) => {
const controllers = initEntityControllers(usecases);
const router = Router();
router.get('/', controllers.getAll as any);
router.get('/:id', controllers.getById as any);
router.post('/', controllers.create as any);
router.put('/:id', controllers.update as any);
router.delete('/:id', controllers.delete as any);
return router;
};
File: src/rest/routes.ts
Add:
import { initEntityRoutes } from './{entity-name}/routes.js';
export const createRestRoutes = (baseContext: BaseContext): Router => {
const router = Router();
const usecases = initUsecases();
router.use(createAuthMiddleware(baseContext));
// Add your entity routes
router.use('/{entities}', initEntityRoutes(usecases));
return router;
};
File: src/docs/api/openapi.yaml
Add the new endpoints to document your API: