Getting Started
Getting Started
Section titled “Getting Started”This section guides you through installing NZOTH, adding it to a NestJS project, and creating your first typed controller.
Installation
Section titled “Installation”Install the server dependencies:
pnpm add @lonestone/nzothQuick example
Section titled “Quick example”Declare a Zod schema and a controller using NZOTH’s typed decorators.
Generate an OpenAPI document from your NestJS app and NZOTH decorators, enabling easy API documentation. Add zod validations to globals filters.
import { createOpenApiDocument, ZodSerializationExceptionFilter, ZodValidationExceptionFilter } from '@lonestone/nzoth/server'
// main.ts
import { NestFactory } from '@nestjs/core'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.useGlobalFilters(
new ZodValidationExceptionFilter(),
new ZodSerializationExceptionFilter(),
)
const swaggerConfig = new DocumentBuilder()
.setOpenAPIVersion('3.1.0')
.setTitle('Lonestone API')
.setDescription('The Lonestone API description')
.setVersion('1.0')
.addTag('@lonestone')
.build()
const document = createOpenApiDocument(app, swaggerConfig, {
override: ({ jsonSchema, zodSchema, io }) => {
const def = zodSchema._zod.def
if (def.type === 'date' && io === 'output') {
jsonSchema.type = 'string'
jsonSchema.format = 'date-time'
}
},
allowEmptySchema: {
custom: true,
},
})
SwaggerModule.setup('docs', app, document, {
jsonDocumentUrl: '/docs-json',
customSiteTitle: 'Lonestone API Documentation',
customfavIcon: '/favicon.ico',
customJs: [
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.11.8/swagger-ui-bundle.min.js',
],
customCssUrl: [
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.11.8/swagger-ui.min.css',
],
swaggerOptions: {
docExpansion: 'list',
filter: true,
showRequestDuration: true,
persistAuthorization: true,
displayOperationId: false,
defaultModelsExpandDepth: 3,
defaultModelExpandDepth: 3,
defaultModelRendering: 'model',
tagsSorter: 'alpha',
operationsSorter: 'alpha',
},
})
await app.listen(3000)
}
bootstrap()
Defines a Zod schema for a user object, specifying that a user has a UUID id and a valid email address.
// modules/post/post.contract.ts
import {
paginatedSchema,
} from '@lonestone/nzoth/server'
import { z } from 'zod'
export const PostSchema = z
.object({
id: z.uuid(),
title: z.string().min(2),
description: z.string().min(2),
content: z.string().min(2),
})
.meta({
title: 'Post',
description: 'Post schema',
})
export type Post = z.infer<typeof PostSchema>
export const PostsSchema = paginatedSchema(PostSchema.omit({ id: true })).meta({
title: 'Posts paginated',
description: 'Posts paginated schema',
})
export type Posts = z.infer<typeof PostsSchema>
export const PostCreateSchema = z
.object({
title: z.string().min(2),
description: z.string().min(2),
content: z.string().min(2),
})
.meta({
title: 'PostCreate',
description: 'Post create schema',
})
export type PostCreate = z.infer<typeof PostCreateSchema>
export const PostUpdateSchema = z
.object({
title: z.string().min(2),
description: z.string().min(2),
content: z.string().min(2),
})
.meta({
title: 'PostUpdate',
description: 'Post update schema',
})
export type PostUpdate = z.infer<typeof PostUpdateSchema>
Use the TypedRoute decorator with schema to add validation and type-safe
modules/post/post.controller.ts
// modules/post/post.controller.ts
import {
TypedBody,
TypedController,
TypedParam,
TypedQuery,
TypedRoute,
} from '@lonestone/nzoth/server'
import { z } from 'zod'
import {
PostCreate,
PostCreateSchema,
Posts,
PostSchema,
PostsSchema,
PostUpdate,
PostUpdateSchema,
} from './post.contract'
@TypedController(
'posts',
)
export class PostController {
@TypedRoute.Get('', PostsSchema)
findAll(
@TypedQuery('q', z.string().optional()) q: string,
): Posts {
const filteredPosts = [
{
id: '1',
title: 'Post 1',
description: 'Post 1 description',
content: 'Post 1 content',
},
].filter(post => post.title.includes(q))
return {
data: filteredPosts,
meta: {
offset: 0,
pageSize: 10,
itemCount: filteredPosts.length,
hasMore: false,
},
}
}
@TypedRoute.Get(':id', PostSchema)
findOne(
@TypedParam('id', z.uuid()) id: string,
) {
return [
{
id: '1',
title: 'Post 1',
description: 'Post 1 description',
content: 'Post 1 content',
},
].find(post => post.id === id)
}
@TypedRoute.Post('', PostSchema)
create(
@TypedBody(PostCreateSchema) postData: PostCreate,
) {
return {
id: '123e4567-e89b-12d3-a456-426614174000',
...postData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
}
@TypedRoute.Put(':id', PostUpdateSchema)
update(
@TypedParam('id', z.uuid()) id: string,
@TypedBody(PostUpdateSchema) postData: PostUpdate,
) {
return {
id,
...postData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
}
@TypedRoute.Delete(':id')
remove(
@TypedParam('id', z.uuid()) id: string,
) {
return id
}
}