Skip to content
Zomer Gregorio

Zomer Gregorio

Software Engineer

Resume
Frontend
  • TypeScript
  • React
  • Next.js
  • TanStack
  • Tailwind CSS
Backend
  • Node.js
  • Hono
  • Express
  • Django
Data
  • PostgreSQL
  • Redis
  • Drizzle
  • Prisma
Infrastructure / Tools
  • Docker
  • GitHub Actions
  • Cloudflare
  • Vercel
Blog

Building Scalable Backends: NestJS, AWS, OpenAPI, and DX Integration

· nestjs · aws · backend · typescript · openapi

Discover how to integrate NestJS, AWS S3, and OpenAPI for robust backend development. Enhance developer experience with best practices for configuration and documentation.

Stay updated

Get a short note when I publish something new. Your email or browser subscription is stored only to deliver these updates; unsubscribe anytime. No account or tracking profile is required.

A confirmation email is required before notifications begin.

Modern web development demands robust, scalable, and well-documented backends. This post demonstrates how to seamlessly integrate NestJS, Amazon Web Services (AWS), and OpenAPI for a superior developer experience (DX) within a single application context.

NestJS: The Backend Foundation

NestJS provides a powerful, opinionated framework for building scalable server-side applications with TypeScript. Its modular architecture and strong adherence to design patterns like dependency injection foster maintainable and testable codebases.

Let's start with a basic health check endpoint, a common practice for application monitoring.

// src/app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { ApiTags, ApiResponse } from '@nestjs/swagger';
 
@ApiTags('Health')
@Controller('health')
export class AppController {
  constructor(private readonly appService: AppService) {}
 
  @Get()
  @ApiResponse({ status: 200, description: 'Application is healthy.' })
  getHealth(): string {
    return this.appService.getHealth();
  }
}
 
// src/app.service.ts
import { Injectable } from '@nestjs/common';
 
@Injectable()
export class AppService {
  getHealth(): string {
    return 'OK';
  }
}

AWS S3: Scalable Cloud Storage

Amazon S3 offers highly available, durable, and scalable object storage, ideal for static assets or user-uploaded files. Integrating S3 allows your NestJS application to offload file management, ensuring scalability and reliability.

We'll use the official AWS SDK v3 to upload a file to an S3 bucket. Configuration is managed via NestJS's ConfigService for better DX.

// src/aws/s3.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { ConfigService } from '@nestjs/config';
 
@Injectable()
export class S3Service {
  private readonly s3Client: S3Client;
  private readonly logger = new Logger(S3Service.name);
  private readonly bucketName: string;
 
  constructor(private configService: ConfigService) {
    this.bucketName = this.configService.get<string>('AWS_S3_BUCKET_NAME');
    this.s3Client = new S3Client({
      region: this.configService.get<string>('AWS_REGION'),
      credentials: {
        accessKeyId: this.configService.get<string>('AWS_ACCESS_KEY_ID'),
        secretAccessKey: this.configService.get<string>('AWS_SECRET_ACCESS_KEY'),
      },
    });
  }
 
  async uploadFile(filename: string, fileBuffer: Buffer, mimetype: string): Promise<string> {
    const uploadParams = {
      Bucket: this.bucketName,
      Key: filename,
      Body: fileBuffer,
      ContentType: mimetype,
      ACL: 'public-read', // Or restrict as needed
    };
 
    try {
      await this.s3Client.send(new PutObjectCommand(uploadParams));
      const fileUrl = `https://${this.bucketName}.s3.${this.configService.get<string>('AWS_REGION')}.amazonaws.com/${filename}`;
      this.logger.log(`File uploaded successfully: ${fileUrl}`);
      return fileUrl;
    } catch (error) {
      this.logger.error(`Error uploading file to S3: ${error.message}`);
      throw error;
    }
  }
}

Now, expose this functionality through a controller, utilizing NestJS's FileInterceptor for multipart form data.

// src/files/files.controller.ts
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { S3Service } from '../aws/s3.service';
import { ApiTags, ApiConsumes, ApiBody, ApiResponse } from '@nestjs/swagger';
 
@ApiTags('Files')
@Controller('files')
export class FilesController {
  constructor(private readonly s3Service: S3Service) {}
 
  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  @ApiConsumes('multipart/form-data')
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        file: {
          type: 'string',
          format: 'binary',
        },
      },
    },
  })
  @ApiResponse({ status: 201, description: 'File uploaded successfully.' })
  @ApiResponse({ status: 500, description: 'Internal server error.' })
  async uploadFile(@UploadedFile() file: Express.Multer.File) {
    const fileUrl = await this.s3Service.uploadFile(file.originalname, file.buffer, file.mimetype);
    return { message: 'File uploaded successfully', url: fileUrl };
  }
}

Documentation & Developer Experience (DX)

Clear, interactive documentation is crucial for a great DX. OpenAPI (Swagger) provides a standard, language-agnostic interface for REST APIs. NestJS integrates seamlessly with @nestjs/swagger to generate this documentation automatically from your code.

Configure Swagger in your main.ts and add decorators to your controllers and DTOs to enrich the generated documentation.

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    bufferLogs: true,
  });
  app.useLogger(app.get(Logger));
 
  const configService = app.get(ConfigService);
  const port = configService.get<number>('PORT') || 3000;
 
  const config = new DocumentBuilder()
    .setTitle('NestJS AWS Integration API')
    .setDescription('API documentation for NestJS application integrating with AWS services.')
    .setVersion('1.0')
    .addTag('Health', 'Health check endpoints')
    .addTag('Files', 'File management endpoints')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document); // Access at /api
 
  await app.listen(port);
  Logger.log(`Application is running on: ${await app.getUrl()}`);
  Logger.log(`Swagger UI available at: ${await app.getUrl()}/api`);
}
bootstrap();

For a complete DX, ensure you use NestJS's ConfigModule for environment variable management and its built-in Logger for structured logging. These practices make your application easier to configure, monitor, and debug, especially in cloud environments like AWS.

Conclusion

By integrating NestJS for a structured backend, AWS for scalable infrastructure, and OpenAPI for comprehensive documentation, you build a robust, maintainable, and developer-friendly application. This combination empowers teams to deliver high-quality services with enhanced agility and operational clarity.