Tutorial REST API dengan NestJS + MongoDB Part 1 — CRUD

Belajar membuat API dengan Nest JS dan MongoDB

Hudya
13 min readApr 9, 2021

Halo semuaa kembali lagi bersama saya Kiddy! Udah masuk kuartal pertama tahun 2021 sejauh apa kita sudah berkembang? Kalau belum mari kita segera perbaiki diri sendiri yuk!

DISCLAIMER: TUTORIAL INI AKAN PANJANG SEKALI KARENA BANYAK YANG DIBAHAS SECARA MENDALAM

Pada kuartal tahun pertama saya sempat melakukan interview dengan sebuah startup dimana startup tersebut menggunakan teknologi NestJS, sebenernya teknologi NestJS ini ngga asing di telinga saya pada tahun 2020. Nah NestJS ini merupakan framework NodeJS yang berbasis Typescript.

“Siapanya javascript tuh bang?”

Typescript itu adalah versi lainnya atau sebuah bahasa pemrograman yang berbasis dari si kakak, yaitu Javascript. Tujuan dibuatnya Typescript ya ingin membuat penulisan jadi semakin lebih rapih, dan mudah dibaca. Eits klaim ini ngga sembarang klaim loh, saya sendiri mengakui setelah mencoba NestJS, secara kodingan lebih rapih ketimbang si kakak, saya bakalan jatuh hati sama Typescript deh hahaha.

Untuk tau lebih lanjut tentang kenapa kamu harus pake Typescript silahkan kunjungi artikel dibawah ini:

Oke sekarang kita kenalan dulu sama si NestJS-nya sebelum kita mengoding, penting banget nih untuk tahu siapa dan apa sih NestJS ini.

NestJS Banner

NestJS, framework NodeJS yang logonya koceng ini emang salah satu framework yang lagi anget-anget t̵a̵i̵ kucing. Sampai saat ini, NestJS sudah mencapai lebih dari 20ribu bintang di Github dan rata-rata download di NPM hingga lebih dari 180ribu, gile ngga tuh!

NestJS ini adalah framework yang highly opinionated.

“apaan lagi tuh bang maksudnya?”

Maksudnya adalah framework ini punya paradigma designnya sendiri, ibarat Laravel yang sudah kental sekali dengan MVC-nya, NestJS ini juga punya MVC ala NestJS, tentu berbeda dengan framework seperti Express JS atau Fastify JS dimana kedua framework ini adalah un-opinonated framework yang artinya, kalian bebas membuat pattern apapun dan gaya apapun untuk membuat projek kalian. Tentunya bagi kamu yang pemula, framework NestJS ini akan lebih mudah dipelajari ketimbang framework yang un-opinionated.

Sebagai penantang baru sebagai salah satu framework yang kece, NestJS ini bisa dibilang banyak mengadopsi banyak hal dari Angular, teknologi yang diadopsi oleh Microsoft, dan dengan menggunakan Typescript.

Nest mengadopsi tiga teknik programming:

  • OOP (Object Oriented Programming)
  • FP (Functional Programming)
  • FRP (Functional Reactive Programming)

Ngga tanggung-tanggung lebih kerennya lagi NestJS ini support API Express JS dan bahkan Fastify (FAVORIT SAYA)! Jadi kamu bisa menjalankan Nest dengan performa Fastify sebagai HTTP Servernya, apa ngga gila tuh? Menurut saya ini keren banget!

Apa impresi pertama saya membuat CRUD pake NestJS? Kagum banget.

Kenapa bang?

Saya biasanya menggunakan pattern Controller ->Service -> Interface -> Repository dimana controller akan melempar logic ke service, lalu apabila perintahnya adalah memasukkan data ke database, maka data akan dilempar ke interface dimana yang awalnya dari object programming menjadi sebuah objek model sesuai atribut yang kita perlukan, dan akhirnya object yang telah disesuaikan dengan model akan disimpan melalui repository. Tanpa perlu membuat struktur seperti itu, Nest sudah menyiapkannya untuk saya ketika saya ngulik Todo List dengan MongoDB. Saya benar-benar takjub dibuatnya oleh Nest, dan saya langsung jatuh hati. 😙

Tanpa basa-basi kita langsung persiapan koding ya!

Pertama kita install dulu nestnya:

npm i -g @nestjs/cli

Catatan: Kalau kamu menggunakan ubuntu, jangan lupa install dengan Sudo karena hak aksesnya error apabila install npm/nodejsnya dari APT, atau buat hak akses npm ke user.

Kalau sudah kita akan buat projek baru dengan mengetikkan kode dibawah ini:

nest new <nama-projek>

ganti nama projeknya dengan nama belajar-todo misalnya, lalu Nest akan menanyakan apakah untuk package managernya pilih saja NPM, setelahnya nest aja menjalankan semua perintah untuk instalasi, tinggal kita tunggu saja s̵a̵m̵p̵a̵i̵ ̵d̵a̵p̵a̵t̵ ̵p̵a̵c̵a̵r̵.

Setelah instalasi berhasil, buka editor code favorit lalu p̵a̵k̵a̵i̵ ̵k̵a̵o̵s̵ ̵p̵a̵r̵t̵a̵i̵m̵u̵, no no, tapi siapkan kopimu hehehehe.

Ini adalah penampakan dari main file NestJS itu sendiri beserta strukturnya, sekarang kita coba untuk melihat sedikit strukturnya.

Pada folder src, kita dapat melihat file controller, module, dan service. Sebagian dari kalian mungkin bingung, apa sih maksudnya ini? Tapi nanti saya jelaskan ya hahaha.

Sekarang kita akan lanjut menggunakan sihir dari NestJS CLI, ya layaknya laravel, NestJS ini memiliki sihir yang dapat membuat sentuhan sihir layaknya Django-nya Python, atau Laravel-nya PHP.

nest generate resource todo

Kamu akan ditanyakan tipe layer seperti apa yang kamu butuhkan, kita pilih saja REST API.

Kemudian akan muncul pertanyaan kedua, apakah kamu ingin otomatis menghasilkan endpoint CRUD? tulis saja “Y”, udah kaya perempuan lagi ngambek.

Sekarang kita lihat hasilnya:

Layaknya sihir Django yang bisa membuat app sendiri, ataupun artisannya laravel yang bisa membuat resource sendiri, NestJS juga punya itu sehingga ini keren banget. Sekarang kita akan mulai membahas strukturnya terlebih dahulu.

Kurang lebih kita melihat ada folder todo pada folder src projek kita. Nah saya akan bahas isi-isinya agar tidak membingungkan berdasarkan kitab suci NestJS.

Controllers

src: https://docs.nestjs.com/controllers

Kalo yang satu ini mah udah kaga usah dibahas kali ya, udah pada mateng banget apa itu controller, yang intinya deh kalo dijelasin ini akan jadi routing pertama ketika aplikasi kalian menerima request dari client secara HTTP.

Nah berbeda dengan Laravel yang masih memiliki file routes.php, NestJS hanya perlu memasukkan routing pada controllernya, jadi ngga ribet deh pokoknya.

Providers

src: https://docs.nestjs.com/providers

Providers adalah bagian dari Nest dimana semua logic diabstraksi pada bagian ini, cukup berbeda dengan Laravel dimana logic business dilakukan pada Controller, namun pada Nest ini dilakukan pada bagian providers, dimana kalau kita liat namanya adalah <namafile>.service.ts.

Modules

src: https://docs.nestjs.com/modules

Modules adalah bagian dari nest dimana semua file dari resource yang kita buat dikumpulkan kedalam sebuah file, tujuannya adalah untuk memudahkan organisir file kalian sehingga ketika nanti module ini diimport, semua bagian dari module tersebut mulai dari controller, service, hingga entity akan dapat diakses dan dapat digabungkan menjadi sebuah kesatuan aplikasi. Contohnya, bisa saja meskipun kita menggunakan Todo sebagai sebuah kesatuan, tapi pada service Todo terdapat TodoController dan TodoUserController, hal ini memungkinkan untuk digabungkan pada satu kesatuan module Todo.

Entities

Kalian lihat file todo.entity.ts? Ya, file ini adalah entitas sebuah file, bisa diibaratkan sebagai model yang biasa kita gunakan pada ORM SQL, atau ODM NoSQL.

DTO (Data Transfer Object)

Tidak asing lagi melihat nama DTO, hal ini memudahkan programmer untuk memfilter object apa yang boleh masuk kedalam providers, jadi DTO ini akan sebagai enkapsulasi dari request yang berasalah pada controller, jika kalian buka file todo.controller.ts kita dapat melihat pada baris 11 pada fungsi create dimana body menerima object class CreateTodoDto, jadi dengan mudah daripada kita definisikan satu-persatu object body yang ingin kita kirim ke business logic, DTO akan membantu kita membuat semuanya jadi lebih mudah, dan cepat. Keren ya NestJS?

Oke kita lanjut, sebelum kita lanjut, kita perlu melakukan instalasi terhadap dua file berikut yaitu mongoose sebagai ODM MongoDB NestJS, dan rxjs.

npm install --save @nestjs/mongoose mongoosenpm install --save rxjs

Sekarang kita berangkat ke app.module.ts terlebih dahulu, ubah kode pada app.module.ts hingga menjadi seperti ini.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TodoModule } from './todo/todo.module';
@Module({
imports: [TodoModule, MongooseModule.forRoot('mongodb://localhost/nest')],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

Kita menambahkan mongoose module untuk mengkoneksikan mongoose dengan database milik kita.

Setelahnya kita buat folder baru didalam folder todo, beri nama schemas, dan setelahnya buat file bernama todo.schema.ts, ini akan menjadi Schema dari collection milik Todo:

import * as mongoose from 'mongoose';export const TodoSchema = new mongoose.Schema({
title: { type: String, index: true },
description: { type: String, default: null },
status: { type: String, default: "TODO", index: true },
timestamp: { type: Number, default: Date.now },
});

Berikut adalah gambar folder dan isi file schema.

Sekarang kita buat file baru seperti schema, kita beri nama todo.interface.ts dan terletak didalam folder todo/interfaces, lalu isi sebagai berikut:

import { Document } from 'mongoose';export class Todo extends Document {
readonly title: String;
readonly description: String;
}

Mungkin masih ada yang bingung kenapa harus ada schema dan interface, kenapa tidak jadi satu saja? Schema berfungsi layaknya model di ORM, namun pada tutorial kali ini, sedangkan interface merupakan sebuah file yang sifatnya observable alias hanya digunakan untuk read-only dan tidak dapat dirubah isinya, tujuannya adalah agar tidak ada nilai yang akan diubah lagi sebelum masuk ke database, sehingga ODM hanya melihat key dan values dari dalam interface yaitu todo.interface.ts.

Sekarang kita ubah file dto milik kita, kita akan mengubah yang create-nya saja.

“Kok create doang bang?”

Karena yang update sudah mengambil create sebagai parent, jadi kita hanya perlu mengubah yang createnya saja, mengubahnya pun tidak perlu repot-report, karena kita sudah membuat interface, interface tersebut dapat kita gunakan sebagai parent dimana class CreateTodoDto akan menurunkan sifat dari interface todo.

import { Todo } from "../interfaces/todo.interface";export class CreateTodoDto extends Todo {}

Interface Todo yang sudah membuat object menjadi observable dapat digunakan oleh CreateTodoDto sebagai base object yang dikirim dari controller, sehingga kita tidak perlu membuat atribut baru di CreateTodoDto terkecuali ada atribut yang perlu kita tambah secara custom.

Sekarang lihat file update-todo.dto.ts. Secara garis besar, UpdateTodoDto sudah menurunkan sifat dari CreateTodoDto, dan class CreateTodoDto sudah menurunkan sifat dari Todo Interface, jadi kita tidak perlu oprek-oprek lagi, ini kerennya NestJS! Terkecuali kembali lagi, apabila kamu butuh tambahan atribut custom, kamu dapat mengisi file tersebut.

Penampakan update-todo.dto.ts:

Sekarang kita mulai masuk kedalam logic businessnya, kita akan oprek keseluruhan dari file todo.service.ts, dimana ini merupakan logic CRUD dari API kita.

import { Injectable } from '@nestjs/common';
import { CreateTodoDto } from './dto/create-todo.dto';
import { UpdateTodoDto } from './dto/update-todo.dto';
import { Model } from 'mongoose';
import { Todo } from './entities/todo.entity'
import { InjectModel } from '@nestjs/mongoose';
import { TodoTransformer } from './transformer/todo.transformer';
@Injectable()
export class TodoService {
constructor(@InjectModel('Todo') private TodoModel: Model<Todo>) { }async create(createTodoDto: CreateTodoDto): Promise<TodoTransformer> {
let data = new this.TodoModel(createTodoDto)
return TodoTransformer.singleTransform(await data.save())
}
async findAll(): Promise<TodoTransformer> {
let data = await this.TodoModel.find()
if (data.length < 1) {
return []
}
return TodoTransformer.transform(data)
}
async findOne(id: string): Promise<TodoTransformer> {
console.log(id)
let data = await this.TodoModel.findById(id)
if (!data) {
throw new Error('Data not found!')
}
return TodoTransformer.singleTransform(data)
}
async update(id: string, updateTodoDto: UpdateTodoDto): Promise<TodoTransformer> {
let data = await this.TodoModel.findByIdAndUpdate(id, updateTodoDto, { 'new': true })

if (!data) {
throw new Error("Todo is not found!")
}
return TodoTransformer.singleTransform(data)
}
async remove(id: string): Promise<String> {
let data = await this.TodoModel.findByIdAndRemove(id)

if (!data) {
throw new Error("Todo is not found!")
}
return "Todo has been deleted!"
}
}

Disini kita mengubah semua fungsi yang tadinya tidak asynchronous menjadi asynchronous, tujuannya agar kita dapat menunggu proses sebelum kita masukkan kedalam transformer untuk diubah menjadi object response.

Kita juga membuat safe return type dimana setiap fungsi diharuskan mengembalikan promise dan bertipe TodoTransformer.

Sekarang pergi ke todo.module.ts, dan ubah isinya menjadi sebagai berikut:

import { Module } from '@nestjs/common';
import { TodoService } from './todo.service';
import { TodoController } from './todo.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { TodoSchema } from './schemas/todo.schema';
@Module({
imports: [MongooseModule.forFeature([{ name: 'Todo', schema: TodoSchema }])],
controllers: [TodoController],
providers: [TodoService]
})
export class TodoModule {}

File module ini membuat kita dapat menggabungkan keseluruhan dari service Todo itu sendiri, sehingga Schema Todo dapat diimport dan digunakan pada keseluruhan project API untuk mengakses collection Todo di MongoDB.

Tentunya kalian melihat ada sesuatu yang salah pada service ts, tenang saja, jangan panik, itu karena file transformer belum ditemukan, dan kita harus membuatnya.

Sebenernya file transformer ini tidak wajib, biasanya pada tutorial-tutorial di Internet object response akan selalu dilemparkan sebagai hasil ke JSON, tapi bagi saya transformer ini mandatory, dikarenakan ini adalah layer terakhir untuk mengotak-atik object sebelum dilempar sebagai JSON.

Sebagai contoh, mobile atau frontend developer meminta kita menambahkan satu key is_author yang berisi boolean true atau false, dimana is_author ini adalah komparasi user_id dari token JWT yang dikirimkan dengan pemilik dari sebuah post, tentu saja kita tidak dapat menggunakan base object dari ODM, melainkan perlu diolah terlebih dahulu untuk menambahkan key is_author dan membuat kondisi apabila token user_id yang dikirimkan adalah pemilik post, maka value-nya true.

Sehingga bagi saya seorang backend, file transformer ini adalah mandatory dan tidak bisa ditawar.

Sudah bacotnya, kita kembali ke root folder yaitu src, sekarang kita buat sebuah file transformer.base.ts yang berisi kode berikut:

export class BaseTransformer {
static transform(data) {
const array = []
data.forEach(element => {
array.push(this.singleTransform(element))
})
return array
}
static singleTransform(element) { }
}

Disini saya membuat sebuah file base transformer sebagai parent dimana sifat transform dapat diturunkan ke childrennya nanti. Pada tutorial-tutorial API yang saya sering buat, saya juga sering membuat file seperti ini kok xixixi.

Berikut penempatannya agar teman-teman tidak bingung.

Sekarang kembali ke folder todo, buat sebuah folder bernama transformers, dan buat file bernama todo.transformer.ts, lalu masukkan kode berikut.

import { BaseTransformer } from "src/transformer.base"export class TodoTransformer extends BaseTransformer {
static singleTransform (element) {
return {
id: element.id,
title: element.title,
description: element.description ?? "",
timestamp: element.timestamp
}
}
}

Todo transformer ini mewariskan sifat dari BaseTransformer, dimana parent memiliki fungsi transform, hal ini membuat kita tidak perlu lagi menuliskan fungsi transform berulang-ulang kali terkecuali konteks penggunaannya agak berbeda. Kita hanya perlu mengoverride atau menimpa fungsi singleTransform, karena setiap service tentu memiliki behavior yang berbeda.

Berikut penampakannya.

Kita sudah hampir selesai, yeay! Sekarang kita harus membuat sebuah base app response dimana kita hanya perlu menggunakan satu base yang sama, pergi ke folder src, dan buat sebuah file bernama response.base.ts, lalu masukkan kode berikut

import { Res, HttpStatus, Response } from '@nestjs/common';export class AppResponse {
static ok(@Res() res, values: any, message: String = ""): Response {
return res.status(HttpStatus.OK).json({
"values": values,
"message": message
})
}
static badRequest(@Res() res, values: any, message: String = ""): Response {
return res.status(HttpStatus.BAD_REQUEST).json({
"values": values,
"message": message
})
}
}

Berikut penampakan tata letak filenya.

Cukup satu fungsi terlebih dahulu, skrg kita beralih ke controller, yaitu todo/todo.controller.ts lalu rombak kode controller kalian dengan kode berikut.

import { Controller, Get, Post, Body, Param, Delete, Res, Put } from '@nestjs/common';
import { TodoService } from './todo.service';
import { CreateTodoDto } from './dto/create-todo.dto';
import { UpdateTodoDto } from './dto/update-todo.dto';
import { AppResponse } from 'src/response.base';
@Controller('todo')
export class TodoController {
constructor(private readonly todoService: TodoService) { }
@Post()
async create(@Res() res, @Body() createTodoDto: CreateTodoDto) {
try {
let data = await this.todoService.create(createTodoDto)
return AppResponse.ok(res, data, "Success create todo!")
} catch (e) {
return AppResponse.badRequest(res, "", e.message)
}
}
@Get()
async findAll(@Res() res) {
try {
let data = await this.todoService.findAll();
return AppResponse.ok(res, data)
} catch (e) {
return AppResponse.badRequest(res, "", e.message)
}
}
@Get(':id')
async findOne(@Res() res, @Param('id') id: string) {
try {
let data = await this.todoService.findOne(id);
return AppResponse.ok(res, data)
} catch (e) {
return AppResponse.badRequest(res, "", e.message)
}
}
@Put(':id')
async update(@Res() res, @Param('id') id: string, @Body() updateTodoDto: UpdateTodoDto) {
try {
let data = await this.todoService.update(id, updateTodoDto);
return AppResponse.ok(res, data, "Todo has been updated!")
} catch (e) {
return AppResponse.badRequest(res, "", e.message)
}
}
@Delete(':id')
async remove(@Res() res, @Param('id') id: string) {
try {
let data = await this.todoService.remove(id);
return AppResponse.ok(res, "", data)
} catch (e) {
return AppResponse.badRequest(res, "", e.message)
}
}
}

Sekarang kita jalankan projeknya dengan command berikut:

npm run start:dev

Notes: Saya menggunakan port 6000 karena port 3000 sudah saya gunakan untuk layanan lainnya, kamu dapat mengganti port yang digunakan pada file main.ts.

Berikut adalah hasilnya:

Jadi gimana? Gampang kan membuat API dengan NestJS, tentunya code kita menjadi semakin rapih dan sedemikian rupa indah dipandang mata, dan secara maintenance, jadi akan semakin lebih mudah.

Tentunya untuk projek skala besar, NestJS masih perlu dioprek dan diperbaiki dengan pattern-pattern yang lebih baik, tapi secara garis besar kita sudah dapat membuat projek sederhana dengan Typescript yaitu NestJS.

Untuk yang ingin clone juga boleh, bisa lihat disini ya:

Untuk tutorial selanjutnya saya akan coba membuat JWT dan autentikasi dengan menggunakan NestJS! Stay tune dan happy coding!

--

--

Hudya

Which is more difficult, coding or counting? Not both of them, the difficult one is sharing your knowledge to people without asking the payment.