Konsep Inti
Controllers (Pengontrol)
// Controller Dasar
import { Controller, Get, Post, Body, Param } from '@nestjs/common'
@Controller('users')
export class UsersController {
@Get()
findAll() {
return ['user1', 'user2']
}
@Get(':id')
findOne(@Param('id') id: string) {
return `Pengguna ${id}`
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return 'Pengguna berhasil dibuat'
}
}
// DTOs (Objek Transfer Data)
export class CreateUserDto {
readonly name: string
readonly email: string
readonly password: string
}
// Pipe Validasi
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator'
export class CreateUserDto {
@IsNotEmpty()
readonly name: string
@IsEmail()
readonly email: string
@MinLength(6)
readonly password: string
}
Providers (Layanan)
// Layanan
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './user.entity'
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>
) {}
async findAll(): Promise<User[]> {
return this.usersRepository.find()
}
async findOne(id: number): Promise<User> {
return this.usersRepository.findOne({ where: { id } })
}
async create(createUserDto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(createUserDto)
return this.usersRepository.save(user)
}
}
// Modul
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
Middleware
// Middleware Kustom
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`Permintaan ${req.method} ${req.url}`)
next()
}
}
// Menerapkan Middleware
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('users')
}
}
Guards (Penjaga)
// Guard Autentikasi
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'
import { Observable } from 'rxjs'
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest()
return this.validateRequest(request)
}
private validateRequest(request: any): boolean {
// Tambahkan logika autentikasi di sini
return true
}
}
// Menggunakan Guards
@Controller('users')
@UseGuards(AuthGuard)
export class UsersController {
// Rute yang dilindungi
}
Interceptors (Pencegat)
// Interceptor Transform
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
export interface Response<T> {
data: T
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map((data) => ({ data })))
}
}
// Cache Interceptor
@Injectable()
export class CacheInterceptor implements NestInterceptor {
constructor(private cacheService: CacheService) {}
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const key = this.generateCacheKey(context)
const cachedResponse = await this.cacheService.get(key)
if (cachedResponse) {
return of(cachedResponse)
}
return next.handle().pipe(tap((response) => this.cacheService.set(key, response)))
}
}
Integrasi Database
Konfigurasi TypeORM
// Entitas
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column({ unique: true })
email: string
@Column()
password: string
@Column({ default: true })
isActive: boolean
}
// Konfigurasi Modul
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'mydb',
entities: [User],
synchronize: true,
}),
],
})
export class AppModule {}
Pola Repository
// Repository Kustom
import { EntityRepository, Repository } from 'typeorm'
import { User } from './user.entity'
@EntityRepository(User)
export class UserRepository extends Repository<User> {
async findByEmail(email: string): Promise<User> {
return this.findOne({ where: { email } })
}
async createUser(createUserDto: CreateUserDto): Promise<User> {
const user = this.create(createUserDto)
await this.save(user)
return user
}
}
Autentikasi
Implementasi JWT
// Layanan Auth
import { Injectable } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.usersService.findByEmail(email)
if (user && (await bcrypt.compare(password, user.password))) {
const { password, ...result } = user
return result
}
return null
}
async login(user: any) {
const payload = { email: user.email, sub: user.id }
return {
access_token: this.jwtService.sign(payload),
}
}
}
// Strategi JWT
import { ExtractJwt, Strategy } from 'passport-jwt'
import { PassportStrategy } from '@nestjs/passport'
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'kunci-rahasia-anda',
})
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email }
}
}
Penanganan Exception
// Filter Exception Kustom
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'
import { Response } from 'express'
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse<Response>()
const status = exception.getStatus()
const error = exception.getResponse()
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
error: error,
})
}
}
// Filter Exception Global
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.useGlobalFilters(new HttpExceptionFilter())
await app.listen(3000)
}
Validasi dan Pipes
// Pipe Validasi Kustom
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'
import { validate } from 'class-validator'
import { plainToClass } from 'class-transformer'
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value
}
const object = plainToClass(metatype, value)
const errors = await validate(object)
if (errors.length > 0) {
throw new BadRequestException('Validasi gagal')
}
return value
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object]
return !types.includes(metatype)
}
}
WebSockets
// Gateway
import { WebSocketGateway, SubscribeMessage, MessageBody } from '@nestjs/websockets'
@WebSocketGateway()
export class ChatGateway {
@SubscribeMessage('message')
handleMessage(@MessageBody() data: string): string {
return data
}
@SubscribeMessage('joinRoom')
handleJoinRoom(@MessageBody() data: any) {
// Tangani logika bergabung dengan ruangan
}
}
// Koneksi Klien
import { WebSocketGateway, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets'
import { Socket } from 'socket.io'
@WebSocketGateway()
export class EventsGateway implements OnGatewayConnection, OnGatewayDisconnect {
handleConnection(client: Socket) {
console.log(`Klien terhubung: ${client.id}`)
}
handleDisconnect(client: Socket) {
console.log(`Klien terputus: ${client.id}`)
}
}
Pengujian
// Unit Testing
import { Test, TestingModule } from '@nestjs/testing'
import { UsersController } from './users.controller'
import { UsersService } from './users.service'
describe('UsersController', () => {
let controller: UsersController
let service: UsersService
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
}).compile()
controller = module.get<UsersController>(UsersController)
service = module.get<UsersService>(UsersService)
})
it('harus terdefinisi', () => {
expect(controller).toBeDefined()
})
it('harus mengembalikan array pengguna', async () => {
const result = ['user1', 'user2']
jest.spyOn(service, 'findAll').mockImplementation(() => result)
expect(await controller.findAll()).toBe(result)
})
})
// Pengujian E2E
import * as request from 'supertest'
import { Test } from '@nestjs/testing'
import { AppModule } from './../src/app.module'
import { INestApplication } from '@nestjs/common'
describe('AppController (e2e)', () => {
let app: INestApplication
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile()
app = moduleFixture.createNestApplication()
await app.init()
})
it('/users (GET)', () => {
return request(app.getHttpServer())
.get('/users')
.expect(200)
.expect('Content-Type', /json/)
.expect([])
})
})
Sumber Daya
Dokumentasi Resmi
Sumber Belajar
Tools dan Extensions
Sumber Komunitas