VovkRequest
type
Request object accepted by controller methods is represented by VovkRequest
type. It extends NextRequest
type from Next.js by defining input body
and query
(but also params
, see below) types. You can use it to strictly type built-in response methods like req.json()
and req.nextUrl.searchParams
that originally purely typed as any
and string | null
.
import { put, type VovkRequest } from 'vovk';
export default class UserController {
@put('{id}')
static async updateUser(req: VovkRequest<{ foo: 'foo' }, { bar: 'bar' }>, { id }: { id: string }) {
const data = await req.json(); // data type is { foo: 'foo' }
const bar = req.nextUrl.searchParams.get('bar'); // bar type is 'bar'
// ...
}
}
Validation libraries define type of request by themselves so you donβt usually need to use VovkRequest
manually.
Also notice the second argument of the handler method. It is used to define the type of parameters extracted from the request URL.
req.vovk
To avoid tweaking the original NextRequest
object, Vovk introduces req.vovk
object that contains additional utilities for request processing. Some validation libraries (like vovk-dto) can override the functions to return a different type, such as a DTO class instance.
async req.vovk.body()
Function req.vovk.body
returns the request body serialized as an object. In most cases, it behaves the same as req.json()
method.
import { post, type VovkRequest } from 'vovk';
export default class UserController {
@post()
static async createUser(req: VovkRequest<{ foo: string }>) {
const body = await req.vovk.body(); // body type is { foo: string }
// ...
}
}
req.vovk.query()
Function req.vovk.query
returns serialized query parameters of the request.
import { get, type VovkRequest } from 'vovk';
export default class UserController {
@get()
static async getUser(req: VovkRequest<null, { id: string }>) {
const query = req.vovk.query(); // query type is { id: string }
// ...
}
}
Nested queries
The nested data is serialized as a query string with square brackets. Itβs commonly known as βPHP-style query string notationβ or βbracket notationβ.
In this notation:
- Square brackets
[ ]
are used to denote keys for arrays or nested objects. - Sequential numeric indices (e.g.,
[0]
,[1]
) represent array elements. - Named keys (e.g.,
[f]
,[u]
) represent properties of an object. - The structure can be nested to arbitrary depth, allowing for complex hierarchical data.
The following query string:
?simple=value&array[0]=first&array[1]=second&object[key]=value&nested[obj][prop]=data&nested[arr][0]=item1&nested[arr][1]=item2&complex[items][0][name]=product&complex[items][0][price]=9.99&complex[items][0][tags][0]=new&complex[items][0][tags][1]=featured
Is parsed as:
{
simple: "value",
array: ["first", "second"],
object: {
key: "value"
},
nested: {
obj: {
prop: "data"
},
arr: ["item1", "item2"]
},
complex: {
items: [
{
name: "product",
price: "9.99",
tags: ["new", "featured"]
}
]
}
}
req.vovk.params()
Function req.vovk.params
returns serialized parameters of the request. In order to define proper typing for it you need to use 3rd generic argument for VovkRequest
type.
import { get, type VovkRequest } from 'vovk';
export default class UserController {
@get('{id}')
static async getUser(req: VovkRequest<null, null, { id: string }>) {
const params = req.vovk.params(); // params type is { id: string }
// ...
}
}
async req.vovk.form()
Function req.vovk.form
returns FormData
of the request serialized as an object. Itβs a type-safe alternative to req.formData()
that reads form data and uses Object.fromEntries(formData.entries())
to convert it to a normal object. The generic argument defines the shape of the form data.
interface Data {
foo: string;
bar: string;
file: File;
}
const formDataObject = await req.vovk.form<Data>();
// the same as
// const body = await req.formData();
// const formDataObject = Object.fromEntries(body.entries()) as Data;
When validation libraries is used with isForm
option, the req.vovk.form
method will be typed automatically.
req.vovk.meta()
Function req.vovk.meta
allows to get and set meta information of the request. The only argument is optional and, if provided, it sets meta information extending the existing meta information and returns the current. If empty, it returns the current meta information. In order to define the type of meta information, you need to pass a generic type.
interface RequestMeta {
foo: string;
bar: string;
currentUser: User;
}
// set foo and bar meta information
req.vovk.meta<RequestMeta>({ foo: 'foo', bar: 'bar' });
// set currentUser meta information
req.vovk.meta<RequestMeta>({ currentUser: user });
// get meta information
const meta = req.vovk.meta<RequestMeta>(); // meta type is RequestMeta including foo, bar and currentUser
The metadata is usually set in a custom decorator.
Metadata set by client
Besides normal inputs such as body
, query
, and params
, RPC module methods also accept meta
object as an input that is sent by setting x-meta
header with JSON-serialized data of this object. On the server side this header is parsed and set as xMetaHeader
property of the metadata object.
On client-side, pass meta
object to the RPC method:
import { UserRPC } from 'vovk-client';
const user = await UserRPC.getUsers({
meta: { hello: 'world' },
});
On server-side, you can access it like this:
import { get, type VovkRequest } from 'vovk';
export default class UserController {
@get()
static async getUsers(req: VovkRequest) {
const { xMetaHeader } = req.vovk.meta<{ xMetaHeader: { hello: string } }>();
console.log(xMetaHeader); // Outputs: { hello: 'world' }
// ...
}
}