How to expose prisma schema to swagger UI in NestJS?

3 min read 05-10-2024
How to expose prisma schema to swagger UI in NestJS?


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:

  1. Install Dependencies: Make sure you have the necessary packages installed.

    npm install @nestjs/swagger @prisma/client
    
  2. 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) } });
      }
    }
    
  3. 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,
        };
      }
    }
    
  4. 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) } });
      }
    }
    
  5. 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 the Post 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.