Skip to content

NSC Events API Documentation Guide

Nahoma edited this page Jan 27, 2026 · 1 revision

1. Overview

1.1 What This Document Covers

This guide is designed for new team members joining the NSC Events project. By the end of this document, you will understand:

  • How our API documentation is generated and served
  • The decorators and patterns we use to document endpoints
  • How to add documentation when creating new endpoints
  • Common troubleshooting steps for documentation issues

1.2 Why We Use OpenAPI/Swagger

We use OpenAPI (via Swagger) for several important reasons:

  1. Single Source of Truth: The API documentation lives in the code itself. When you update an endpoint, the documentation updates automaticallyβ€”no separate wiki to maintain.

  2. Interactive Testing: Swagger UI provides a "Try it out" feature. You can test endpoints directly in your browser without needing Postman or curl commands.

  3. Frontend-Backend Alignment: Frontend developers can explore the exact request/response shapes expected by each endpoint, reducing miscommunication.

  4. Onboarding Acceleration: New developers can explore the entire API surface within minutes of cloning the repo.

  5. JWT Authentication Testing: Our Swagger setup includes "Authorize" functionalityβ€”you can paste a JWT token once and test authenticated endpoints seamlessly.

1.3 Architecture: How It Works

We use a Code-First approach to API documentation:

flowchart LR
    A[TypeScript Decorators] --> B[NestJS Scanner]
    B --> C[OpenAPI 3.0 Spec]
    C --> D[Swagger UI]
Loading

Here's what happens when you start the server:

  1. TypeScript Decorators: You annotate controllers, methods, and DTOs with decorators like @ApiOperation(), @ApiResponse(), and @ApiProperty().

  2. NestJS Scanner: On application startup, NestJS scans all registered modules and extracts decorator metadata.

  3. OpenAPI Spec: The SwaggerModule compiles this metadata into a JSON document following the OpenAPI 3.0 specification.

  4. Swagger UI: This JSON spec is served through an interactive web interface where you can explore and test the API.


2. Accessing the Documentation

2.1 Local Development

Once the backend server is running, you can access:

Resource URL
Swagger UI http://localhost:3000/api/docs
Raw OpenAPI JSON http://localhost:3000/api/docs-json

Note: The port may vary if you've configured a different PORT environment variable.

2.2 Environment Behavior

Currently, Swagger is enabled in all environments (development, test, and production). This is configured in src/main.ts:

// Swagger configuration (from src/main.ts)
const config = new DocumentBuilder()
  .setTitle('NSC Events API')
  .setDescription('API documentation for NSC Events management system')
  .setVersion('2.0.0')
  .addBearerAuth(
    {
      type: 'http',
      scheme: 'bearer',
      bearerFormat: 'JWT',
      name: 'JWT',
      description: 'Enter JWT token',
      in: 'header',
    },
    'JWT-auth',
  )
  .addTag('Authentication', 'User authentication and authorization endpoints')
  .addTag('Users', 'User management endpoints')
  .addTag('Events', 'Event/Activity management endpoints')
  .addTag('Event Registration', 'Event registration and attendance endpoints')
  .addTag('Google Auth', 'Google Calendar integration endpoints')
  .build();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document, {
  swaggerOptions: {
    persistAuthorization: true,  // Keeps your JWT token across page refreshes
    tagsSorter: 'alpha',         // Sorts API tags alphabetically
    operationsSorter: 'alpha',   // Sorts operations alphabetically
  },
  customSiteTitle: 'NSC Events API Documentation',
});

TODO: Consider disabling Swagger in production for security. You could wrap the Swagger setup in a condition like:

if (process.env.NODE_ENV !== 'production') {
  // Swagger setup here
}

3. Documentation Standards

3.1 Controller-Level Decorators

Every controller should have these decorators at the class level:

@ApiTags() - Grouping Endpoints

Groups all endpoints in this controller under a labeled section in Swagger UI.

// From src/auth/controllers/auth.controller.ts
@ApiTags('Authentication')
@Controller('auth')
export class AuthController {
  // ...
}
// From src/activity/controllers/activity/activity.controller.ts
@ApiTags('Events')
@Controller('events')
export class ActivityController {
  // ...
}

@ApiBearerAuth() - Marking Authenticated Routes

When a controller or endpoint requires JWT authentication, use this decorator. The string 'JWT-auth' must match the security scheme name defined in main.ts.

// From src/user/controllers/user/user.controller.ts
@ApiTags('Users')
@ApiBearerAuth('JWT-auth')  // All endpoints in this controller require auth
@UseGuards(AuthGuard('jwt'))
@Controller('users')
export class UserController {
  // ...
}

For individual endpoints within an otherwise public controller:

// From src/auth/controllers/auth.controller.ts
@UseGuards(AuthGuard('jwt'))
@ApiBearerAuth('JWT-auth')  // Only this endpoint requires auth
@Post('/change-password')
async changePassword(...) { }

3.2 Endpoint-Level Decorators

Each endpoint should be documented with these decorators:

@ApiOperation() - Describing the Endpoint

Provides a summary and description for the endpoint.

// From src/auth/controllers/auth.controller.ts
@Post('/signup')
@ApiOperation({
  summary: 'Register a new user',
  description: 'Creates a new user account and returns a JWT token',
})
signUp(@Body() signUpDto: SignUpDto): Promise<{ token: string }> {
  return this.authService.register(signUpDto);
}

@ApiResponse() - Documenting Response Codes

Document all possible response codes, including success and error cases.

// From src/auth/controllers/auth.controller.ts
@Post('/signup')
@ApiResponse({
  status: 201,
  description: 'User successfully registered',
  schema: {
    type: 'object',
    properties: {
      token: {
        type: 'string',
        example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
      },
    },
  },
})
@ApiResponse({ status: 400, description: 'Invalid input data' })
@ApiResponse({ status: 409, description: 'User already exists' })

@ApiParam() - Documenting URL Parameters

// From src/activity/controllers/activity/activity.controller.ts
@Get('find/:id')
@ApiParam({ name: 'id', description: 'Event ID (UUID)' })
async findActivityById(@Param('id') id: string): Promise<Activity> {
  return this.activityService.getActivityById(id);
}

@ApiQuery() - Documenting Query Parameters

// From src/activity/controllers/activity/activity.controller.ts
@Get('')
@ApiQuery({
  name: 'page',
  required: false,
  description: 'Page number',
  example: '1',
})
@ApiQuery({
  name: 'tags',
  required: false,
  description: 'Filter by tags (comma-separated)',
  example: 'club,study',
})
async getAllActivities(
  @Query('page') page = '1',
  @Query('tags') tagsParam?: string,
): Promise<Activity[]> { }

@ApiBody() - Documenting Request Bodies

// From src/auth/controllers/auth.controller.ts
@Post('/login')
@ApiBody({ type: LoginDto })
login(@Body() loginDto: LoginDto): Promise<{ token: string }> {
  return this.authService.login(loginDto);
}

Marking Deprecated Endpoints

// From src/activity/controllers/activity/activity.controller.ts
@Post('attend/:id')
@ApiOperation({
  summary: 'Attend event (DEPRECATED)',
  description: 'DEPRECATED: Use /api/event-registration/attend instead',
  deprecated: true,
})
async attendEvent(...) { }

3.3 DTO-Level Decorators

The Golden Rule: If a property isn't decorated with @ApiProperty(), it won't appear in Swagger.

Required Properties

// From src/auth/dto/signup.dto.ts
export class SignUpDto {
  @ApiProperty({
    description: 'User first name',
    example: 'John',
  })
  @IsNotEmpty()
  @IsString()
  readonly firstName: string;

  @ApiProperty({
    description: 'User email address',
    example: 'john.doe@example.com',
  })
  @IsNotEmpty()
  @IsEmail({}, { message: 'Please enter correct email' })
  readonly email: string;
}

Optional Properties

// From src/user/dto/update-user.dto.ts
export class UpdateUserDto {
  @ApiProperty({
    description: 'User first name',
    example: 'John',
    required: false,  // Mark as optional in Swagger
  })
  @IsOptional()       // Validation: field is optional
  @IsString()
  readonly firstName: string;
}

Properties with Constraints

// From src/auth/dto/login.dto.ts
@ApiProperty({
  description: 'User password (minimum 8 characters)',
  example: 'Password123!',
  minLength: 8,
})
@IsNotEmpty()
@IsString()
@MinLength(8)
readonly password: string;

Enum Properties

// From src/auth/dto/signup.dto.ts
@ApiProperty({
  description: 'User role',
  enum: Role,
  example: Role.user,
})
@IsNotEmpty()
@IsString()
readonly role: Role;

Where Role is defined as:

// From src/user/entities/user.entity.ts
export enum Role {
  admin = 'admin',
  creator = 'creator',
  user = 'user',
}

Properties with Default Values

// From src/event-registration/dto/create-event-registration.dto.ts
@ApiProperty({
  description: 'Whether the user has attended the event',
  example: false,
  default: false,
  required: false,
})
@IsBoolean()
@IsOptional()
isAttended = false;

3.4 File Uploads

For endpoints that accept file uploads, we use special decorators:

@ApiConsumes('multipart/form-data') - Declaring Content Type

// From src/activity/controllers/activity/activity.controller.ts
@Post('new')
@UseGuards(AuthGuard())
@ApiBearerAuth('JWT-auth')
@UseInterceptors(FileInterceptor('coverImage'))
@ApiConsumes('multipart/form-data')
@ApiOperation({
  summary: 'Create new event',
  description: 'Creates a new event. Supports file upload for cover image.',
})

@ApiBody() with Binary Schema - Describing File Fields

// From src/activity/controllers/activity/activity.controller.ts
@Put(':id/cover-image')
@UseInterceptors(FileInterceptor('coverImage'))
@ApiConsumes('multipart/form-data')
@ApiBody({
  schema: {
    type: 'object',
    properties: {
      coverImage: {
        type: 'string',
        format: 'binary',
        description: 'Cover image file',
      },
    },
    required: ['coverImage'],
  },
})
async uploadCoverImage(...) { }

For complex forms with both file and text fields:

// From src/activity/controllers/activity/activity.controller.ts (create event)
@ApiBody({
  schema: {
    type: 'object',
    properties: {
      eventTitle: { type: 'string', example: 'Tech Workshop 2025' },
      eventDescription: { type: 'string', example: 'A workshop on modern web technologies' },
      startDate: { type: 'string', format: 'date-time', example: '2025-12-01T14:00:00Z' },
      coverImage: {
        type: 'string',
        format: 'binary',
        description: 'Cover image file (optional)',
      },
      // ... more fields
    },
    required: ['eventTitle', 'eventDescription', 'startDate', /* ... */],
  },
})

3.5 DTOs Missing @ApiProperty Decorators

TODO: The following DTOs have validation decorators but are missing @ApiProperty() decorators. These should be added for complete Swagger documentation:

  • src/activity/dto/create-activity.dto.ts
  • src/activity/dto/update-activity.dto.ts

These DTOs use inline @ApiBody({ schema: {...} }) in the controller as a workaround, but ideally the DTOs themselves should be fully decorated.


4. Validation Alignment

There's an important relationship between validation decorators (from class-validator) and documentation decorators (from @nestjs/swagger). They should tell the same story.

Example: Properly Aligned DTO

// From src/auth/dto/login.dto.ts
export class LoginDto {
  @ApiProperty({
    description: 'User email address',
    example: 'user@example.com',
  })
  @IsNotEmpty()  // Validation: required
  @IsEmail({}, { message: 'Please enter correct email' })  // Validation: must be email
  readonly email: string;

  @ApiProperty({
    description: 'User password (minimum 8 characters)',
    example: 'Password123!',
    minLength: 8,  // Documentation: minimum length
  })
  @IsNotEmpty()      // Validation: required
  @IsString()        // Validation: must be string
  @MinLength(8)      // Validation: minimum 8 characters
  readonly password: string;
}

Key alignment points:

Validation Decorator Corresponding @ApiProperty Option
@IsNotEmpty() required: true (default)
@IsOptional() required: false
@MinLength(n) minLength: n
@MaxLength(n) maxLength: n
@IsEnum(Enum) enum: Enum
@IsEmail() Consider adding format: 'email'

Why This Matters

When validation and documentation are misaligned:

  • Frontend developers may send invalid data because docs said it was optional
  • Users get confusing error messages
  • Testing becomes unreliable

5. Adding a New Documented Endpoint (Step-by-Step)

Use this checklist when adding a new endpoint:

Pre-Implementation

  • Identify which controller this endpoint belongs to
  • Determine if it requires authentication
  • Design the request/response DTOs

Controller Setup

  • Add @ApiTags('YourModule') to the controller class (if not already present)
  • Add @ApiBearerAuth('JWT-auth') if the endpoint requires authentication

Endpoint Documentation

  • Add @ApiOperation({ summary: '...', description: '...' })
  • Add @ApiResponse() for the success case (200, 201, etc.) with response type/schema
  • Add @ApiResponse() for each error case:
    • 400 - Bad Request (validation errors)
    • 401 - Unauthorized (missing/invalid token)
    • 403 - Forbidden (insufficient permissions)
    • 404 - Not Found
    • 409 - Conflict (duplicate resource)
  • Add @ApiParam() for each URL parameter
  • Add @ApiQuery() for each query parameter
  • Add @ApiBody() referencing your request DTO

DTO Documentation

  • Add @ApiProperty() to every property in your request DTO
  • Add @ApiProperty() to every property in your response DTO (if returning a class)
  • Include description, example, and required (for optional fields)
  • For enums, add enum: YourEnum

Validation

  • Ensure class-validator decorators align with @ApiProperty constraints
  • Test that validation errors are returned correctly

Final Verification

  • Restart the NestJS server
  • Open Swagger UI at http://localhost:3000/api/docs
  • Verify your endpoint appears under the correct tag
  • Verify request body schema is correct
  • Verify response schemas are correct
  • Test the endpoint using "Try it out"

6. Troubleshooting

"My endpoint doesn't appear in Swagger UI"

  1. Is the controller registered in a module?

    // your.module.ts
    @Module({
      controllers: [YourController],  // Must be listed here
    })
    export class YourModule {}
  2. Is the module imported in AppModule?

    // app.module.ts
    @Module({
      imports: [YourModule],  // Must be imported here
    })
    export class AppModule {}
  3. Check for typos in route decorators

    • @Controller('events') β†’ endpoint will be at /api/events
    • @Get('find/:id') β†’ endpoint will be at /api/events/find/:id
  4. Restart the server

    • Swagger documentation is generated at startup. Changes require a restart.

"My DTO properties are missing"

  1. Did you add @ApiProperty() to each property?

    // WRONG - property won't appear
    @IsString()
    readonly name: string;
    
    // CORRECT - property will appear
    @ApiProperty({ description: 'User name' })
    @IsString()
    readonly name: string;
  2. Are you using an interface instead of a class?

    // WRONG - interfaces have no runtime metadata
    interface CreateUserDto {
      name: string;
    }
    
    // CORRECT - classes preserve metadata
    class CreateUserDto {
      @ApiProperty()
      name: string;
    }

"Validation errors don't match documentation"

  1. Check that decorators align

    • If @IsNotEmpty() is present, don't set required: false in @ApiProperty()
    • If @MinLength(8) is present, add minLength: 8 to @ApiProperty()
  2. Ensure ValidationPipe is enabled globally

    // src/main.ts
    app.useGlobalPipes(new ValidationPipe());

"File upload not showing correctly"

  1. Did you add @ApiConsumes('multipart/form-data')?

    @Post('upload')
    @ApiConsumes('multipart/form-data')  // Required for file uploads
    @UseInterceptors(FileInterceptor('file'))
  2. Did you use @ApiBody() with binary format?

    @ApiBody({
      schema: {
        type: 'object',
        properties: {
          file: {
            type: 'string',
            format: 'binary',  // This tells Swagger it's a file
          },
        },
      },
    })

"JWT authentication not working in Swagger"

  1. Click the "Authorize" button at the top of Swagger UI
  2. Enter your JWT token (without the "Bearer " prefixβ€”Swagger adds it automatically)
  3. Click "Authorize"
  4. The lock icons should now show as locked for authenticated endpoints

7. API Tags Reference

Our API is organized into these sections:

Tag Description Controller
Authentication User registration, login, password management AuthController
Users User profile management (admin functions) UserController
Events Event/Activity CRUD operations ActivityController
Event Registration Event registration and attendance EventRegistrationController
Google Auth Google Calendar integration GoogleAuthController

8. Further Reading


Appendix: Quick Reference

Common Decorator Imports

import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiBearerAuth,
  ApiParam,
  ApiQuery,
  ApiBody,
  ApiConsumes,
  ApiProperty,
} from '@nestjs/swagger';

Minimal Documented Endpoint

@ApiTags('Example')
@Controller('example')
export class ExampleController {
  @Get(':id')
  @ApiOperation({ summary: 'Get item by ID' })
  @ApiParam({ name: 'id', description: 'Item ID' })
  @ApiResponse({ status: 200, description: 'Item found' })
  @ApiResponse({ status: 404, description: 'Item not found' })
  getItem(@Param('id') id: string) {
    // implementation
  }
}

Minimal Documented DTO

export class CreateItemDto {
  @ApiProperty({
    description: 'Item name',
    example: 'My Item',
  })
  @IsNotEmpty()
  @IsString()
  name: string;

  @ApiProperty({
    description: 'Item description',
    example: 'A detailed description',
    required: false,
  })
  @IsOptional()
  @IsString()
  description?: string;
}

Clone this wiki locally