Services
A service is part of the Controller–Service–Repository pattern. It keeps business logic out of the request handlers: the controller deals with HTTP, and the service does the work and changes the data.
Like controllers, services are often classes with static methods, but they need no decorators and no special structure. The static class is only a convention: you can use class instances, standalone functions or plain objects instead. The pattern does not need dependency injection (DI) either: a service can be a plain module that you import and call.
Take this controller:
import { z } from 'zod';
import { procedure, prefix, post, operation } from 'vovk';
import UserService from './user-service';
@prefix('users')
export default class UserController {
@operation({
summary: 'Update user',
description: 'Update user by ID with Zod validation',
})
@post('{id}')
static updateUser = procedure({
body: z.object({ /* ... */ }),
params: z.object({ /* ... */ }),
query: z.object({ /* ... */ }),
output: z.object({ /* ... */ }),
}).handle(async (req) => {
const body = await req.vovk.body();
const query = req.vovk.query();
const params = req.vovk.params();
return UserService.updateUser(body, query, params);
});
}The handle method returns the result of UserService.updateUser, and that method takes its types from the procedure. So the validation schemas (Zod here) are the single source of truth for the input and output types, and you define no separate types. Thanks to Anders Hejlsberg for the fix in #58616 : without this TypeScript change, Vovk.ts would not be possible.
import type { VovkBody, VovkOutput, VovkParams, VovkQuery } from 'vovk';
import type UserController from './user-controller';
export default class UserService {
static updateUser(
body: VovkBody<typeof UserController.updateUser>,
query: VovkQuery<typeof UserController.updateUser>,
params: VovkParams<typeof UserController.updateUser>
) {
// database calls or other business logic
console.log(body, query, params);
return { success: true, id: params.id } satisfies VovkOutput<typeof UserController.updateUser>;
}
}Service methods infer their types from procedures, and procedures call service methods, without circular type errors.