Controller

Controller Definition

Controller is a static class that handles incoming HTTP requests. The methods of this class, that are decorated with an HTTP decorator (like @get() or @post()), accept 2 arguments: NextRequest (that is not monkey-patched by Vovk.ts itself) and parameters that are defined by the decorator path.

import type { NextRequest } from 'next';
import { prefix, put } from 'vovk';
 
@prefix('users')
export default class UserController {
    // Example request: PUT /api/users/69?role=moderator
    @put(':id') 
    static async updateUser(req: NextRequest, { id }: { id: string }) {
        const data = await req.json(); // any
        const userRole = req.nextUrl.searchParams.get('role'); // string | null
        // ...
        return updatedUser;
    }
}

At the example aboce data is casted as any and userRole is casted as string | null. To fix the body and query types Vovk.ts provides a new type VovkRequest<BODY?, QUERY?> that is extended from NextRequest where the first generic argument represents the type of value returned from req.json but also allows to define values returned from req.nextUrl.searchParams.get. VovkRequest also plays crucial role in type inference when vovk-client is used in order to infer types properly.

As its mentioned before, req object is an original NextRequest object that provided by Next.js as is, without changing it, but other libraries (like vovk-zod (opens in a new tab)) as well as your custom code can modify this object when needed (for example to add currentUser property defined by your auth guard decorator).

To add the required body and query types just replace NextRequest by VovkRequest. Let's modify the abstract example above.

/src/modules/user/UserController.ts
import { prefix, put, type VovkRequest } from 'vovk';
import type { User } from '../../types';
 
@prefix('users')
export default class UserController {
    // Example request: PUT /api/users/69?role=moderator
    @put(':id') 
    static async updateUser(
        req: VovkRequest<Partial<User>, { role: 'user' | 'moderator' | 'admin' }>, 
        { id }: { id: string }
    ) {
        const data = await req.json(); // Partial<User>
        const userRole = req.nextUrl.searchParams.get('role'); // 'user' | 'moderator' | 'admin'
        // ...
        return updatedUser;
    }
}

As you can see we've changed nothing more than the type of req but now data receives type of Partial<User> and userRole is casted as 'user' | 'moderator' | 'admin' and does not extend null anymore.

Controller Initialization

Once the controller is defined it needs to be initialized at the wildcard route by adding it to the controllers object.

/src/app/api/[[...vovk]]/route.ts
import { initVovk } from 'vovk';
import UserController from '../../../modules/user/UserController';
 
const controllers = { UserController };
const workers = {}; // See Worker documentation
 
export type Controllers = typeof controllers;
export type Workers = typeof workers;
 
export const { GET, POST, PUT, DELETE } = initVovk({ controllers, workers });

Auto-generated Endpoints

All HTTP decorators provide .auto method that generates endpoint name automatically from the method name.

/src/modules/user/UserController.ts
import { prefix, put } from 'vovk';
 
@prefix('users')
export default class UserController {
    // Example request: PUT /api/users/do-something
    @put.auto() 
    static async doSomething(/* ... */) {
        // ...
    }
}

Since the client is generated automatically, if your API is used only internally, you can use auto-generated endpoints to save a little bit of time thinking of new endpoint name.

Response Headers

All HTTP decorators support custom response headers provided as the second argument.

// ...
export default class UserController {
    @put('do-something', { headers: { 'x-hello': 'world' } }) 
    static async doSomething(/* ... */) { /* ... */ }
}

To enable CORS instead of manually setting up headers you can use cors: true option.

// ...
export default class UserController {
    @put('do-something', { cors: true }) 
    static async doSomething(/* ... */) { /* ... */ }
}

For auto-generated endpoints cors and headers are defined as the only argument.

// ...
export default class UserController {
    @put.auto({ cors: true, headers: { 'x-hello': 'world' } }) 
    static async doSomething(/* ... */) { /* ... */ }
}

Dynamic Response Headers

To set up dynamic response headers you can use NextResponse object that is provided by Next.js.

import { NextResponse } from 'next/server';
 
// ...
export default class UserController {
    @put('do-something') 
    static async doSomething() {
        return NextResponse.json({ hello: 'world' }, { 'x-hello': 'world' });
    }
}