Hono API patterns for DNUM-SocialGouv (routes/controllers/services/schemas/types/tests, openapi helpers, zValidator, prisma). Trigger on "hono", "route", "controller", "service", "schema", "openapi".
Use this skill when adding or changing backend endpoints built with Hono in this repo.
apps/backend/src/features/<feature-name>/<feature-name>.<file-type>.tsroute, controller, service, schema, type, test@sirena/backend-utils/helpers.Example:
export const getUserRoute = openApiProtectedRoute({
description: 'Get user by id',
responses: {
...openApiResponse(GetUserResponseSchema),
...openApi404NotFound('User not found'),
},
});
paginationQueryParamsSchema for search/limit/offset/order.Example:
export const UserSchema = z.object({
id: z.cuid(),
email: z.email({ message: 'Invalid email address' }),
prenom: z.string(),
nom: z.string(),
uid: z.string(),
sub: z.string(),
pcData: z.record(z.string(), z.string()),
roleId: z.string(),
statutId: z.string(),
entiteId: z.string().nullable(),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
});
const columns = [
Prisma.UserScalarFieldEnum.email,
Prisma.UserScalarFieldEnum.prenom,
Prisma.UserScalarFieldEnum.nom,
] as const;
export const GetUsersQuerySchema = paginationQueryParamsSchema(columns).extend({
roleId: z
.string()
.transform((val) => val.split(',').map((id) => id.trim()))
.optional(),
statutId: z
.string()
.transform((val) => val.split(',').map((id) => id.trim()))
.optional(),
});
z.infer from schemas.Example:
export type GetUsersQuery = z.infer<typeof GetUsersQuerySchema>;
factoryWithLogs.createApp() for typed context.zValidator for query/body validation.c.json({ data: ... }, status).authMiddleware (auth cookie/session)userStatusMiddleware (active user checks)roleMiddleware([ROLES...]) (RBAC guard)entitesMiddleware (entite context)pino.middleware (logging)sentry.middleware (error context)upload.middleware (multipart handling)logout.middlewarechangelog/* (entity change tracking)Example:
const app = factoryWithLogs
.createApp()
.use(authMiddleware)
.use(userStatusMiddleware)
.use(roleMiddleware([ROLES.SUPER_ADMIN, ROLES.ENTITY_ADMIN]))
.use(entitesMiddleware)
.get('/:id', getUsersRoute, zValidator('query', GetUsersQuerySchema), async (c) => {
// ...
return c.json({ data: users }, 200);
})
.get('/:id', getUserRoute, async (c) => {
// ...
return c.json({ data: user }, 200);
});
service files.Example:
export const getUsers = async (entiteIds: string[] | null, query: GetUsersQuery = {}) => {
const { offset = 0, limit, sort = 'nom', order = 'asc', roleId, statutId, search } = query;
const entiteFilter = filterByEntities(entiteIds);
const roleFilter = filterByRoles(roleId ?? null);
const searchConditions: Prisma.UserWhereInput[] | undefined = search?.trim()
? [
{ prenom: { contains: search, mode: 'insensitive' } },
{ nom: { contains: search, mode: 'insensitive' } },
{ email: { contains: search, mode: 'insensitive' } },
]
: undefined;
const where: Prisma.UserWhereInput = {
...(entiteFilter ?? {}),
...(roleFilter ?? {}),
...(statutId !== undefined ? { statutId: { in: statutId } } : {}),
...(searchConditions ? { OR: searchConditions } : {}),
};
const [data, total] = await Promise.all([
prisma.user.findMany({
where,
skip: offset,
...(typeof limit === 'number' ? { take: limit } : {}),
orderBy: { [sort]: order },
include: { role: true },
}),
prisma.user.count({ where }),
]);
return { data, total };
};
testClient from hono/testing.appWithLogs.createApp().use(pinoLogger()).route('/', Controller).onError(errorHandler).client.index.$get() or client[':id'].$get() with query/param/json.Example:
describe('Users endpoints: /users', () => {
const app = appWithLogs.createApp().use(pinoLogger()).route('/', UsersController).onError(errorHandler);
const client = testClient(app);
describe('GET /', () => {
it('returns filtered users', async () => {
const res = await client.index.$get({
query: { roleId: ROLES.NATIONAL_STEERING, statutId: 'ACTIF' },
});
// assert status + payload
});
it('returns 404 when user not found', async () => {
// mock service to return null, assert 404 payload
});
});
});
Reference: apps/backend/src/features/users/users.controller.test.ts.
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer