DTO
Hello World with a Service example
Form input handling and class validation with vovk-dto . The request input is validated on the client-side before being sent to the server where it is validated again. Notice that the input data needs to be transformed with class-transformer into a DTO class in order to be validated on the client-side.
Result
Code
1import { IsString, IsNumber, Min, IsEmail, IsUUID, IsIn, IsBoolean, Max } from 'class-validator';
2import { JSONSchema } from 'class-validator-jsonschema';
3
4@JSONSchema({ description: 'User object' })
5export class UpdateUserBodyDto {
6 @IsString()
7 @JSONSchema({ description: 'User full name' })
8 name!: string;
9
10 @IsNumber()
11 @Min(0)
12 @Max(120)
13 @JSONSchema({ description: 'User age' })
14 age!: number;
15
16 @IsEmail()
17 @JSONSchema({ description: 'User email' })
18 email!: string;
19}
20
21@JSONSchema({ description: 'Path parameters' })
22export class UpdateUserParamsDto {
23 @IsUUID()
24 @JSONSchema({ description: 'User ID' })
25 id!: string;
26}
27
28@JSONSchema({ description: 'Query parameters' })
29export class UpdateUserQueryDto {
30 @IsIn(['email', 'push', 'none'])
31 @JSONSchema({ description: 'Notification type' })
32 notify!: string;
33}
34
35@JSONSchema({ description: 'Response object' })
36export class UpdateUserResponseDto {
37 @IsBoolean()
38 @JSONSchema({ description: 'Success status' })
39 success!: boolean;
40}
1import { IsString, IsNumber, Min, IsEmail, IsUUID, IsIn, IsBoolean, Max } from 'class-validator';
2import { JSONSchema } from 'class-validator-jsonschema';
3
4@JSONSchema({ description: 'User object' })
5export class UpdateUserBodyDto {
6 @IsString()
7 @JSONSchema({ description: 'User full name' })
8 name!: string;
9
10 @IsNumber()
11 @Min(0)
12 @Max(120)
13 @JSONSchema({ description: 'User age' })
14 age!: number;
15
16 @IsEmail()
17 @JSONSchema({ description: 'User email' })
18 email!: string;
19}
20
21@JSONSchema({ description: 'Path parameters' })
22export class UpdateUserParamsDto {
23 @IsUUID()
24 @JSONSchema({ description: 'User ID' })
25 id!: string;
26}
27
28@JSONSchema({ description: 'Query parameters' })
29export class UpdateUserQueryDto {
30 @IsIn(['email', 'push', 'none'])
31 @JSONSchema({ description: 'Notification type' })
32 notify!: string;
33}
34
35@JSONSchema({ description: 'Response object' })
36export class UpdateUserResponseDto {
37 @IsBoolean()
38 @JSONSchema({ description: 'Success status' })
39 success!: boolean;
40}
1import { IsString, IsNumber, Min, IsEmail, IsUUID, IsIn, IsBoolean, Max } from 'class-validator';
2import { JSONSchema } from 'class-validator-jsonschema';
3
4@JSONSchema({ description: 'User object' })
5export class UpdateUserBodyDto {
6 @IsString()
7 @JSONSchema({ description: 'User full name' })
8 name!: string;
9
10 @IsNumber()
11 @Min(0)
12 @Max(120)
13 @JSONSchema({ description: 'User age' })
14 age!: number;
15
16 @IsEmail()
17 @JSONSchema({ description: 'User email' })
18 email!: string;
19}
20
21@JSONSchema({ description: 'Path parameters' })
22export class UpdateUserParamsDto {
23 @IsUUID()
24 @JSONSchema({ description: 'User ID' })
25 id!: string;
26}
27
28@JSONSchema({ description: 'Query parameters' })
29export class UpdateUserQueryDto {
30 @IsIn(['email', 'push', 'none'])
31 @JSONSchema({ description: 'Notification type' })
32 notify!: string;
33}
34
35@JSONSchema({ description: 'Response object' })
36export class UpdateUserResponseDto {
37 @IsBoolean()
38 @JSONSchema({ description: 'Success status' })
39 success!: boolean;
40}
1import { IsString, IsNumber, Min, IsEmail, IsUUID, IsIn, IsBoolean, Max } from 'class-validator';
2import { JSONSchema } from 'class-validator-jsonschema';
3
4@JSONSchema({ description: 'User object' })
5export class UpdateUserBodyDto {
6 @IsString()
7 @JSONSchema({ description: 'User full name' })
8 name!: string;
9
10 @IsNumber()
11 @Min(0)
12 @Max(120)
13 @JSONSchema({ description: 'User age' })
14 age!: number;
15
16 @IsEmail()
17 @JSONSchema({ description: 'User email' })
18 email!: string;
19}
20
21@JSONSchema({ description: 'Path parameters' })
22export class UpdateUserParamsDto {
23 @IsUUID()
24 @JSONSchema({ description: 'User ID' })
25 id!: string;
26}
27
28@JSONSchema({ description: 'Query parameters' })
29export class UpdateUserQueryDto {
30 @IsIn(['email', 'push', 'none'])
31 @JSONSchema({ description: 'Notification type' })
32 notify!: string;
33}
34
35@JSONSchema({ description: 'Response object' })
36export class UpdateUserResponseDto {
37 @IsBoolean()
38 @JSONSchema({ description: 'Success status' })
39 success!: boolean;
40}
1import { prefix, post, openapi } from 'vovk';
2import { withDto } from 'vovk-dto';
3import { UpdateUserBodyDto, UpdateUserParamsDto, UpdateUserQueryDto, UpdateUserResponseDto } from './UserDto';
4
5@prefix('users-dto')
6export default class UserDtoController {
7 @openapi({
8 summary: 'Update user (DTO)',
9 description: 'Update user by ID with DTO validation',
10 })
11 @post('{id}')
12 static updateUser = withDto({
13 body: UpdateUserBodyDto,
14 params: UpdateUserParamsDto,
15 query: UpdateUserQueryDto,
16 output: UpdateUserResponseDto,
17 async handle(req, { id }) {
18 const { name, age, email } = await req.json();
19 const notify = req.nextUrl.searchParams.get('notify');
20 // do something with the data
21 console.log(`Updating user ${id}:`, { name, age, email, notify });
22 return {
23 success: true,
24 } satisfies UpdateUserResponseDto;
25 },
26 });
27}
1import { prefix, post, openapi } from 'vovk';
2import { withDto } from 'vovk-dto';
3import { UpdateUserBodyDto, UpdateUserParamsDto, UpdateUserQueryDto, UpdateUserResponseDto } from './UserDto';
4
5@prefix('users-dto')
6export default class UserDtoController {
7 @openapi({
8 summary: 'Update user (DTO)',
9 description: 'Update user by ID with DTO validation',
10 })
11 @post('{id}')
12 static updateUser = withDto({
13 body: UpdateUserBodyDto,
14 params: UpdateUserParamsDto,
15 query: UpdateUserQueryDto,
16 output: UpdateUserResponseDto,
17 async handle(req, { id }) {
18 const { name, age, email } = await req.json();
19 const notify = req.nextUrl.searchParams.get('notify');
20 // do something with the data
21 console.log(`Updating user ${id}:`, { name, age, email, notify });
22 return {
23 success: true,
24 } satisfies UpdateUserResponseDto;
25 },
26 });
27}
1import { prefix, post, openapi } from 'vovk';
2import { withDto } from 'vovk-dto';
3import { UpdateUserBodyDto, UpdateUserParamsDto, UpdateUserQueryDto, UpdateUserResponseDto } from './UserDto';
4
5@prefix('users-dto')
6export default class UserDtoController {
7 @openapi({
8 summary: 'Update user (DTO)',
9 description: 'Update user by ID with DTO validation',
10 })
11 @post('{id}')
12 static updateUser = withDto({
13 body: UpdateUserBodyDto,
14 params: UpdateUserParamsDto,
15 query: UpdateUserQueryDto,
16 output: UpdateUserResponseDto,
17 async handle(req, { id }) {
18 const { name, age, email } = await req.json();
19 const notify = req.nextUrl.searchParams.get('notify');
20 // do something with the data
21 console.log(`Updating user ${id}:`, { name, age, email, notify });
22 return {
23 success: true,
24 } satisfies UpdateUserResponseDto;
25 },
26 });
27}
1import { prefix, post, openapi } from 'vovk';
2import { withDto } from 'vovk-dto';
3import { UpdateUserBodyDto, UpdateUserParamsDto, UpdateUserQueryDto, UpdateUserResponseDto } from './UserDto';
4
5@prefix('users-dto')
6export default class UserDtoController {
7 @openapi({
8 summary: 'Update user (DTO)',
9 description: 'Update user by ID with DTO validation',
10 })
11 @post('{id}')
12 static updateUser = withDto({
13 body: UpdateUserBodyDto,
14 params: UpdateUserParamsDto,
15 query: UpdateUserQueryDto,
16 output: UpdateUserResponseDto,
17 async handle(req, { id }) {
18 const { name, age, email } = await req.json();
19 const notify = req.nextUrl.searchParams.get('notify');
20 // do something with the data
21 console.log(`Updating user ${id}:`, { name, age, email, notify });
22 return {
23 success: true,
24 } satisfies UpdateUserResponseDto;
25 },
26 });
27}
1'use client';
2import { useState, type FormEvent } from 'react';
3import { UserDtoRPC } from 'vovk-client';
4import type { VovkReturnType } from 'vovk';
5import { validateOnClient } from 'vovk-dto/validateOnClient';
6import { plainToInstance } from 'class-transformer';
7import { UpdateUserBodyDto, UpdateUserParamsDto, UpdateUserQueryDto } from '@/modules/dto/UserDto';
8
9export default function DtoFormExample() {
10 const [response, setResponse] = useState<VovkReturnType<typeof UserDtoRPC.updateUser> | null>(null);
11 const [error, setError] = useState<Error | null>(null);
12 const [name, setName] = useState('');
13 const [email, setEmail] = useState('');
14 const [age, setAge] = useState(30);
15 const [disableClientValidation, setDisableClientValidation] = useState(false);
16 const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
17 e.preventDefault();
18 try {
19 setResponse(
20 await UserDtoRPC.updateUser({
21 body: plainToInstance(UpdateUserBodyDto, { name, email, age } satisfies UpdateUserBodyDto),
22 query: plainToInstance(UpdateUserQueryDto, { notify: 'push' } satisfies UpdateUserQueryDto),
23 params: plainToInstance(UpdateUserParamsDto, {
24 id: '5a279068-35d6-4d67-94e0-c21ef4052eea',
25 } satisfies UpdateUserParamsDto),
26 disableClientValidation,
27 // vovk.config doesn't include preferred validation library,
28 // so we need to pass it manually for this example.
29 // This is not needed in a real project when config.imports.validateOnClient is set to 'vovk-dto/validateOnClient'.
30 validateOnClient,
31 })
32 );
33 setError(null);
34 } catch (e) {
35 setError(e as Error);
36 setResponse(null);
37 }
38 };
39
40 return (
41 <form onSubmit={onSubmit}>
42 <input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
43 <input type="text" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
44 <label>Age:</label>
45 <input type="number" placeholder="Age" value={age} onChange={(e) => setAge(e.target.valueAsNumber)} />
46 <label className="block mb-4">
47 <input
48 type="checkbox"
49 className="mr-2"
50 checked={disableClientValidation}
51 onChange={(e) => setDisableClientValidation(e.target.checked)}
52 />
53 Disable client-side validation
54 </label>
55 <button>Submit</button>
56
57 {response && (
58 <div className="text-left">
59 <h3>Response:</h3>
60 <pre>{JSON.stringify(response, null, 2)}</pre>
61 </div>
62 )}
63
64 {error && <div className="overflow-auto">❌ {String(error)}</div>}
65 </form>
66 );
67}
1'use client';
2import { useState, type FormEvent } from 'react';
3import { UserDtoRPC } from 'vovk-client';
4import type { VovkReturnType } from 'vovk';
5import { validateOnClient } from 'vovk-dto/validateOnClient';
6import { plainToInstance } from 'class-transformer';
7import { UpdateUserBodyDto, UpdateUserParamsDto, UpdateUserQueryDto } from '@/modules/dto/UserDto';
8
9export default function DtoFormExample() {
10 const [response, setResponse] = useState<VovkReturnType<typeof UserDtoRPC.updateUser> | null>(null);
11 const [error, setError] = useState<Error | null>(null);
12 const [name, setName] = useState('');
13 const [email, setEmail] = useState('');
14 const [age, setAge] = useState(30);
15 const [disableClientValidation, setDisableClientValidation] = useState(false);
16 const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
17 e.preventDefault();
18 try {
19 setResponse(
20 await UserDtoRPC.updateUser({
21 body: plainToInstance(UpdateUserBodyDto, { name, email, age } satisfies UpdateUserBodyDto),
22 query: plainToInstance(UpdateUserQueryDto, { notify: 'push' } satisfies UpdateUserQueryDto),
23 params: plainToInstance(UpdateUserParamsDto, {
24 id: '5a279068-35d6-4d67-94e0-c21ef4052eea',
25 } satisfies UpdateUserParamsDto),
26 disableClientValidation,
27 // vovk.config doesn't include preferred validation library,
28 // so we need to pass it manually for this example.
29 // This is not needed in a real project when config.imports.validateOnClient is set to 'vovk-dto/validateOnClient'.
30 validateOnClient,
31 })
32 );
33 setError(null);
34 } catch (e) {
35 setError(e as Error);
36 setResponse(null);
37 }
38 };
39
40 return (
41 <form onSubmit={onSubmit}>
42 <input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
43 <input type="text" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
44 <label>Age:</label>
45 <input type="number" placeholder="Age" value={age} onChange={(e) => setAge(e.target.valueAsNumber)} />
46 <label className="block mb-4">
47 <input
48 type="checkbox"
49 className="mr-2"
50 checked={disableClientValidation}
51 onChange={(e) => setDisableClientValidation(e.target.checked)}
52 />
53 Disable client-side validation
54 </label>
55 <button>Submit</button>
56
57 {response && (
58 <div className="text-left">
59 <h3>Response:</h3>
60 <pre>{JSON.stringify(response, null, 2)}</pre>
61 </div>
62 )}
63
64 {error && <div className="overflow-auto">❌ {String(error)}</div>}
65 </form>
66 );
67}
1'use client';
2import { useState, type FormEvent } from 'react';
3import { UserDtoRPC } from 'vovk-client';
4import type { VovkReturnType } from 'vovk';
5import { validateOnClient } from 'vovk-dto/validateOnClient';
6import { plainToInstance } from 'class-transformer';
7import { UpdateUserBodyDto, UpdateUserParamsDto, UpdateUserQueryDto } from '@/modules/dto/UserDto';
8
9export default function DtoFormExample() {
10 const [response, setResponse] = useState<VovkReturnType<typeof UserDtoRPC.updateUser> | null>(null);
11 const [error, setError] = useState<Error | null>(null);
12 const [name, setName] = useState('');
13 const [email, setEmail] = useState('');
14 const [age, setAge] = useState(30);
15 const [disableClientValidation, setDisableClientValidation] = useState(false);
16 const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
17 e.preventDefault();
18 try {
19 setResponse(
20 await UserDtoRPC.updateUser({
21 body: plainToInstance(UpdateUserBodyDto, { name, email, age } satisfies UpdateUserBodyDto),
22 query: plainToInstance(UpdateUserQueryDto, { notify: 'push' } satisfies UpdateUserQueryDto),
23 params: plainToInstance(UpdateUserParamsDto, {
24 id: '5a279068-35d6-4d67-94e0-c21ef4052eea',
25 } satisfies UpdateUserParamsDto),
26 disableClientValidation,
27 // vovk.config doesn't include preferred validation library,
28 // so we need to pass it manually for this example.
29 // This is not needed in a real project when config.imports.validateOnClient is set to 'vovk-dto/validateOnClient'.
30 validateOnClient,
31 })
32 );
33 setError(null);
34 } catch (e) {
35 setError(e as Error);
36 setResponse(null);
37 }
38 };
39
40 return (
41 <form onSubmit={onSubmit}>
42 <input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
43 <input type="text" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
44 <label>Age:</label>
45 <input type="number" placeholder="Age" value={age} onChange={(e) => setAge(e.target.valueAsNumber)} />
46 <label className="block mb-4">
47 <input
48 type="checkbox"
49 className="mr-2"
50 checked={disableClientValidation}
51 onChange={(e) => setDisableClientValidation(e.target.checked)}
52 />
53 Disable client-side validation
54 </label>
55 <button>Submit</button>
56
57 {response && (
58 <div className="text-left">
59 <h3>Response:</h3>
60 <pre>{JSON.stringify(response, null, 2)}</pre>
61 </div>
62 )}
63
64 {error && <div className="overflow-auto">❌ {String(error)}</div>}
65 </form>
66 );
67}
1'use client';
2import { useState, type FormEvent } from 'react';
3import { UserDtoRPC } from 'vovk-client';
4import type { VovkReturnType } from 'vovk';
5import { validateOnClient } from 'vovk-dto/validateOnClient';
6import { plainToInstance } from 'class-transformer';
7import { UpdateUserBodyDto, UpdateUserParamsDto, UpdateUserQueryDto } from '@/modules/dto/UserDto';
8
9export default function DtoFormExample() {
10 const [response, setResponse] = useState<VovkReturnType<typeof UserDtoRPC.updateUser> | null>(null);
11 const [error, setError] = useState<Error | null>(null);
12 const [name, setName] = useState('');
13 const [email, setEmail] = useState('');
14 const [age, setAge] = useState(30);
15 const [disableClientValidation, setDisableClientValidation] = useState(false);
16 const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
17 e.preventDefault();
18 try {
19 setResponse(
20 await UserDtoRPC.updateUser({
21 body: plainToInstance(UpdateUserBodyDto, { name, email, age } satisfies UpdateUserBodyDto),
22 query: plainToInstance(UpdateUserQueryDto, { notify: 'push' } satisfies UpdateUserQueryDto),
23 params: plainToInstance(UpdateUserParamsDto, {
24 id: '5a279068-35d6-4d67-94e0-c21ef4052eea',
25 } satisfies UpdateUserParamsDto),
26 disableClientValidation,
27 // vovk.config doesn't include preferred validation library,
28 // so we need to pass it manually for this example.
29 // This is not needed in a real project when config.imports.validateOnClient is set to 'vovk-dto/validateOnClient'.
30 validateOnClient,
31 })
32 );
33 setError(null);
34 } catch (e) {
35 setError(e as Error);
36 setResponse(null);
37 }
38 };
39
40 return (
41 <form onSubmit={onSubmit}>
42 <input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
43 <input type="text" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
44 <label>Age:</label>
45 <input type="number" placeholder="Age" value={age} onChange={(e) => setAge(e.target.valueAsNumber)} />
46 <label className="block mb-4">
47 <input
48 type="checkbox"
49 className="mr-2"
50 checked={disableClientValidation}
51 onChange={(e) => setDisableClientValidation(e.target.checked)}
52 />
53 Disable client-side validation
54 </label>
55 <button>Submit</button>
56
57 {response && (
58 <div className="text-left">
59 <h3>Response:</h3>
60 <pre>{JSON.stringify(response, null, 2)}</pre>
61 </div>
62 )}
63
64 {error && <div className="overflow-auto">❌ {String(error)}</div>}
65 </form>
66 );
67}
Last updated on