Exposing Your Prisma Schema to Swagger UI in NestJS: A Comprehensive Guide
Problem: You're building a NestJS application with Prisma ORM and want to leverage Swagger UI to document your API endpoints. However, you're struggling to expose the underlying Prisma schema definitions within Swagger UI for easy reference.
Rephrased: You want to use Swagger UI to showcase your API endpoints, but also want it to automatically document your data models from Prisma. This makes it easier for developers to understand the structure and relationships of your data.
Setting the Stage: Your NestJS Project
Let's assume you have a basic NestJS application using Prisma to manage your database interactions. Here's a snippet of your Prisma schema:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(auto()) @map("_id")
email String @unique
firstName String
lastName String
}
model Post {
id Int @id @default(auto()) @map("_id")
title String
content String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
The Challenge: Exposing Prisma Schema to Swagger UI
Swagger UI excels at visually representing your API endpoints, but it doesn't natively integrate with Prisma schemas. To bridge this gap, we'll need to use a custom approach.
Solution: Leveraging @nestjs/swagger
and @prisma/client
Here's how to expose your Prisma schema to Swagger UI:
-
Install Dependencies: Make sure you have the necessary packages installed.
npm install @nestjs/swagger @prisma/client
-
Define Your Controllers: Create your NestJS controllers as usual.
import { Controller, Get, Param, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { PrismaService } from './prisma.service'; @ApiTags('Users') @Controller('users') export class UsersController { constructor(private readonly prisma: PrismaService) {} @Get() async findAll(@Query('email') email?: string) { if (email) { return this.prisma.user.findUnique({ where: { email } }); } else { return this.prisma.user.findMany(); } } @Get(':id') async findOne(@Param('id') id: string) { return this.prisma.user.findUnique({ where: { id: parseInt(id) } }); } }
-
Create a Helper Class: We'll create a helper class to generate Swagger definitions from our Prisma models.
import { Injectable } from '@nestjs/common'; import { ApiProperty } from '@nestjs/swagger'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaSchemaHelper { constructor(private prisma: PrismaClient) {} getSwaggerDefinition(model: keyof PrismaClient) { const schema = this.prisma[model].$model; const properties: { [key: string]: any } = {}; Object.entries(schema.fields).forEach(([fieldName, field]) => { properties[fieldName] = { type: field.type.name, ...(field.isList ? { isArray: true } : {}), ...(field.isOptional ? { nullable: true } : {}), description: field.documentation, // Optional: Add field description }; }); return { type: 'object', properties, }; } }
-
Utilize the Helper Class: Inject the helper class into your controllers and utilize its functionality.
import { Controller, Get, Param, Query } from '@nestjs/common'; import { ApiTags, ApiBody, ApiProperty, ApiParam, ApiQuery } from '@nestjs/swagger'; import { PrismaService } from './prisma.service'; import { PrismaSchemaHelper } from './prisma-schema-helper'; @ApiTags('Users') @Controller('users') export class UsersController { constructor( private readonly prisma: PrismaService, private readonly prismaSchemaHelper: PrismaSchemaHelper, ) {} @Get() @ApiQuery({ name: 'email', required: false }) async findAll(@Query('email') email?: string) { if (email) { return this.prisma.user.findUnique({ where: { email } }); } else { return this.prisma.user.findMany(); } } @Get(':id') @ApiParam({ name: 'id', type: 'integer', description: 'User ID', }) async findOne(@Param('id') id: string) { return this.prisma.user.findUnique({ where: { id: parseInt(id) } }); } }
-
Configure Swagger: Register Swagger in your
main.ts
file.import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); const config = new DocumentBuilder() .setTitle('My API') .setDescription('API documentation for my application') .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); await app.listen(3000); } bootstrap();
Now, when you access your Swagger UI (typically at http://localhost:3000/api
), you'll see the automatically generated documentation for your Prisma models. You can use the @ApiProperty
decorator for individual properties to customize their documentation.
Additional Considerations:
- Relationships: This approach automatically handles simple model properties. For relationships (e.g.,
author
in thePost
model), you'll need to manually specify the structure using@ApiProperty
to ensure accurate Swagger representation. - Complexity: For larger schemas with many models and complex relationships, consider using a tool like
prisma-generate-swagger
to automate the schema generation process. - Customization: Leverage
@nestjs/swagger
's decorators for detailed customization of your API documentation, including response types, request body definitions, and error handling.
Conclusion: A More Transparent API with Swagger UI
By exposing your Prisma schema to Swagger UI, you enable developers to understand your data model structure directly within the API documentation. This leads to more transparent and maintainable APIs, fostering better communication and collaboration within your development team.