Cara Membangun, Menguji, Menggunakan, dan Mendokumentasikan REST API Menggunakan Express dan Node

29 October, 2025
97

Halo semuanya! Dalam tutorial ini kita akan menyelami lebih dalam REST API.

Saya baru-baru ini menulis artikel ini di mana saya menjelaskan perbedaan utama antara jenis API umum saat ini. Dan tutorial ini bertujuan untuk menunjukkan kepada Anda contoh bagaimana Anda dapat sepenuhnya mengimplementasikan REST API.

Kami akan membahas pengaturan dan arsitektur dasar dengan Node dan Express, pengujian unit dengan Supertest, melihat bagaimana kami dapat menggunakan API dari aplikasi front-end React dan akhirnya mendokumentasikan API menggunakan alat seperti Swagger.

Perlu diingat bahwa kita tidak akan membahas terlalu dalam bagaimana setiap teknologi bekerja. Tujuannya di sini adalah untuk memberi Anda gambaran umum tentang cara kerja REST API, bagaimana bagian-bagiannya berinteraksi, dan apa yang mungkin terdiri dari implementasi penuh.

Ayo!

Indeks

  • Apa itu ISTIRAHAT?
  • Cara Membangun REST API dengan Node dan Express
  • Cara Menguji REST API dengan Supertest
  • Cara Mengkonsumsi REST API di Aplikasi React Front-end
  • Cara Mendokumentasikan REST API dengan Swagger
  • Membungkus

Apa itu ISTIRAHAT?

Representational State Transfer (REST) adalah gaya arsitektur yang banyak digunakan untuk membangun layanan web dan API.

API RESTful dirancang agar sederhana, dapat diskalakan, dan fleksibel. Mereka sering digunakan dalam aplikasi web dan seluler, serta dalam arsitektur Internet of Things (IoT) dan layanan mikro.

Karakteristik utama:

  1. Stateless: REST API bersifat stateless, yang berarti bahwa setiap permintaan berisi semua informasi yang diperlukan untuk memprosesnya. Hal ini memudahkan untuk menskalakan API dan meningkatkan performa dengan mengurangi kebutuhan untuk menyimpan dan mengelola data sesi di server.
  2. Berbasis sumber daya: REST API berbasis sumber daya, yang berarti bahwa setiap sumber daya diidentifikasi oleh URI unik (Pengidentifikasi Sumber Daya Seragam) dan dapat diakses menggunakan metode HTTP standar seperti GET, POST, PUT, dan DELETE.
  3. Antarmuka Seragam:  REST API memiliki antarmuka seragam yang memungkinkan klien berinteraksi dengan sumber daya menggunakan serangkaian metode dan format respons standar. Hal ini memudahkan pengembang untuk membangun dan memelihara API, dan bagi klien untuk mengkonsumsinya.
  4. Dapat di-cache: REST API dapat di-cache, yang berarti respons dapat di-cache untuk meningkatkan kinerja dan mengurangi lalu lintas jaringan.
  5. Sistem Berlapis: REST API dirancang untuk berlapis, yang berarti bahwa perantara seperti proxy dan gateway dapat ditambahkan antara klien dan server tanpa memengaruhi keseluruhan sistem.

Kelebihan REST API**:**

  • Mudah dipelajari dan digunakan: REST API relatif sederhana dan mudah dipelajari dibandingkan dengan API lainnya.
  • Skalabilitas: Sifat stateless dari REST API membuatnya sangat terukur dan efisien.
  • Fleksibilitas: REST API fleksibel dan dapat digunakan untuk membangun berbagai aplikasi dan sistem.
  • Dukungan luas: REST API didukung secara luas oleh alat dan kerangka kerja pengembangan, sehingga mudah untuk mengintegrasikannya ke dalam sistem yang ada.

Kekurangan REST API**:**

  • Kurangnya standar: Kurangnya standar ketat untuk REST API dapat menyebabkan inkonsistensi dan masalah interoperabilitas.
  • Fungsionalitas terbatas: REST API dirancang untuk menangani permintaan dan respons sederhana dan mungkin tidak cocok untuk kasus penggunaan yang lebih kompleks.
  • Masalah keamanan: REST API dapat rentan terhadap serangan keamanan seperti skrip lintas situs (XSS) dan pemalsuan permintaan lintas situs (CSRF) jika tidak diterapkan dengan benar.

REST API terbaik untuk:****

  • REST API sangat cocok untuk membangun aplikasi web dan seluler, serta arsitektur layanan mikro dan sistem IoT.
  • Mereka sangat berguna dalam situasi di mana skalabilitas dan fleksibilitas penting, dan di mana pengembang perlu berintegrasi dengan sistem dan teknologi yang ada.

Singkatnya, REST API adalah gaya arsitektur yang populer dan banyak digunakan untuk membangun layanan web dan API. Mereka sederhana, dapat diskalakan, dan fleksibel, dan dapat digunakan untuk membangun berbagai aplikasi dan sistem.

Meskipun ada beberapa batasan dan kekhawatiran dengan REST API, mereka tetap menjadi pilihan populer dan efektif untuk membangun API di berbagai industri dan sektor.

Cara Membangun REST API dengan Node dan Express

Alat kami

Node.js adalah lingkungan runtime JavaScript back-end sumber terbuka, lintas platform, yang memungkinkan pengembang mengeksekusi kode JavaScript di luar browser web. Itu dibuat oleh Ryan Dahl pada tahun 2009 dan sejak itu menjadi pilihan populer untuk membangun aplikasi web, API, dan server.

Node.js menyediakan model I/O yang digerakkan oleh peristiwa dan non-pemblokiran yang membuatnya ringan dan efisien, memungkinkannya menangani data dalam jumlah besar dengan kinerja tinggi. Ini juga memiliki komunitas yang besar dan aktif, dengan banyak perpustakaan dan modul yang tersedia untuk membantu pengembang membangun aplikasi mereka dengan lebih cepat dan mudah.

Express.js adalah kerangka kerja aplikasi web populer untuk Node.js, yang digunakan untuk membangun aplikasi web dan API. Ini menyediakan serangkaian fitur dan alat untuk membangun server web, menangani permintaan dan respons HTTP, merutekan permintaan ke penangan tertentu, menangani middleware, dan banyak lagi.

Express dikenal dengan kesederhanaan, fleksibilitas, dan skalabilitasnya, menjadikannya pilihan populer bagi pengembang yang membangun aplikasi web dengan Node.js.

Beberapa fitur dan manfaat utama dari Express.js meliputi:

  • Minimalis dan fleksibel: Express.js menyediakan struktur minimalis dan fleksibel yang memungkinkan pengembang membangun aplikasi seperti yang mereka inginkan.
  • Perutean: Express.js memudahkan untuk menentukan rute untuk menangani permintaan HTTP dan memetakannya ke fungsi atau penangan tertentu.
  • Middleware: Express.js memungkinkan pengembang untuk menentukan fungsi middleware yang dapat digunakan untuk menangani tugas-tugas umum seperti autentikasi, pencatatan, penanganan kesalahan, dan banyak lagi.
  • API yang kuat: Express.js menyediakan API yang kuat untuk menangani permintaan dan respons HTTP, memungkinkan pengembang membangun aplikasi web berkinerja tinggi.

Arsitektur kami

Untuk proyek ini kita akan mengikuti arsitektur lapisan di basis kode kita. Arsitektur lapisan adalah tentang membagi masalah dan tanggung jawab ke dalam folder dan file yang berbeda, dan memungkinkan komunikasi langsung hanya antara folder dan file tertentu.

Masalah berapa banyak lapisan yang harus dimiliki proyek Anda, nama apa yang harus dimiliki setiap lapisan, dan tindakan apa yang harus ditangani adalah masalah diskusi. Jadi mari kita lihat apa yang menurut saya adalah pendekatan yang baik untuk contoh kita.

Aplikasi kami akan memiliki lima lapisan berbeda, yang akan diurutkan dengan cara ini:

Citra

Lapisan aplikasi

  • Lapisan aplikasi akan memiliki pengaturan dasar server kita dan koneksi ke rute kita (lapisan berikutnya).
  • Lapisan rute akan memiliki definisi semua rute kita dan koneksi ke pengontrol (lapisan berikutnya).
  • Lapisan pengontrol akan memiliki logika aktual yang ingin kita lakukan di setiap titik akhir kita dan koneksi ke lapisan model (lapisan berikutnya, Anda mengerti idenya...)
  • Lapisan model akan menyimpan logika untuk berinteraksi dengan database tiruan kita.
  • Akhirnya, lapisan persistensi adalah tempat database kita akan berada.

Hal penting yang perlu diingat adalah bahwa dalam arsitektur semacam ini, ada aliran komunikasi yang ditentukan antara lapisan yang harus diikuti agar masuk akal.

Ini berarti bahwa permintaan pertama-tama harus melalui lapisan pertama, lalu yang kedua, lalu yang ketiga dan seterusnya. Tidak ada permintaan yang boleh melewatkan lapisan karena itu akan mengacaukan logika arsitektur dan manfaat organisasi dan modularitas yang diberikannya kepada kita.

Jika Anda ingin mengetahui beberapa opsi arsitektur API lainnya, saya merekomendasikan artikel arsitektur perangkat lunak yang saya tulis beberapa waktu lalu.

Kode

Sebelum melompat ke kode, mari kita sebutkan apa yang sebenarnya akan kita bangun. Kami akan membangun API untuk bisnis tempat penampungan hewan peliharaan. Tempat penampungan hewan peliharaan ini perlu mendaftarkan hewan peliharaan yang tinggal di tempat penampungan, dan untuk itu kami akan melakukan operasi CRUD dasar (membuat, membaca, memperbarui, dan menghapus).

Sekarang ya, mari kita mulai hal ini. Buat direktori baru, masuk ke dalamnya dan mulai proyek Node baru dengan menjalankan npm init -y.

Kemudian instal Express dengan menjalankan npm i express dan instal nodemon sebagai dependensi dev dengan menjalankan npm i -D nodemon (Nodemon adalah alat yang akan kami gunakan untuk menjalankan server kami dan mengujinya). Terakhir, jalankan juga npm i cors, yang akan kita gunakan untuk dapat menguji server kita secara lokal.

App.js

Keren, sekarang buat file app.js dan jatuhkan kode ini di dalamnya:

import express from 'express'
import cors from 'cors'

import petRoutes from './pets/routes/pets.routes.js'

const app = express()
const port = 3000

/* Global middlewares */
app.use(cors())
app.use(express.json())

/* Routes */
app.use('/pets', petRoutes)

/* Server setup */
if (process.env.NODE_ENV !== 'test') {
    app.listen(port, () => console.log(`⚡️[server]: Server is running at https://localhost:${port}`))
}

export default app

Ini akan menjadi lapisan aplikasi proyek kami.

Di sini kita pada dasarnya menyiapkan server kita dan menyatakan bahwa setiap permintaan yang mencapai arah /pets harus menggunakan rute (titik akhir) yang telah kita deklarasikan di direktori ./pets/routes/pets.routes.js.

Selanjutnya, lanjutkan dan buat struktur folder ini di proyek Anda:

Citra

Struktur folder

Rute

Masuk ke folder rute, buat file bernama pets.routes.js, dan jatuhkan kode ini di dalamnya:

import express from "express";
import {
  listPets,
  getPet,
  editPet,
  addPet,
  deletePet,
} from "../controllers/pets.controllers.js";

const router = express.Router();

router.get("/", listPets);

router.get("/:id", getPet);

router.put("/:id", editPet);

router.post("/", addPet);

router.delete("/:id", deletePet);

export default router;

Dalam file ini kita menginisialisasi router (hal yang memproses permintaan kita dan mengarahkannya sesuai dengan URL titik akhir) dan menyiapkan setiap titik akhir kita.

Lihat bahwa untuk setiap titik akhir kami mendeklarasikan metode HTTP yang sesuai (get, put, dan sebagainya) dan fungsi terkait yang akan dipicu oleh titik akhir tersebut (listPets, getPet, dan sebagainya). Setiap nama fungsi cukup eksplisit sehingga kita dapat dengan mudah mengetahui apa yang dilakukan setiap titik akhir tanpa perlu melihat kode lebih lanjut. ;)

Terakhir, kami juga mendeklarasikan titik akhir mana yang akan menerima parameter URL pada permintaan seperti ini: router.get("/:id", getPet); Di sini kami mengatakan bahwa kami akan menerima id hewan peliharaan sebagai parameter URL.

Controller

Sekarang buka folder pengontrol, buat file pets.controllers.js, dan masukkan kode ini ke dalamnya:

import { getItem, listItems, editItem, addItem, deleteItem } from '../models/pets.models.js'

export const getPet = (req, res) => {
    try {
        const resp = getItem(parseInt(req.params.id))
        res.status(200).json(resp)

    } catch (err) {
        res.status(500).send(err)
    }
}

export const listPets = (req, res) => {
    try {
        const resp = listItems()
        res.status(200).json(resp)

    } catch (err) {
        res.status(500).send(err)
    }
}

export const editPet = (req, res) => {
    try {
        const resp = editItem(parseInt(req.params.id), req.body)
        res.status(200).json(resp)

    } catch (err) {
        res.status(500).send(err)
    }
}

export const addPet = (req, res) => {
    try {
        const resp = addItem(req.body)
        res.status(200).json(resp)

    } catch (err) {
        res.status(500).send(err)
    }
}

export const deletePet = (req, res) => {
    try {
        const resp = deleteItem(parseInt(req.params.id))
        res.status(200).json(resp)

    } catch (err) {
        res.status(500).send(err)
    }
}

Pengontrol adalah fungsi yang akan dipicu oleh setiap permintaan titik akhir. Seperti yang Anda lihat, mereka menerima sebagai parameter objek permintaan dan respons. Dalam objek permintaan kita dapat membaca hal-hal seperti parameter URL atau isi, dan kita akan menggunakan objek respons untuk mengirim respons kita setelah melakukan komputasi yang sesuai.

Setiap pengontrol memanggil fungsi tertentu yang ditentukan dalam model kami.

Model

Sekarang buka folder model dan buat file pets.models.js dengan kode ini di dalamnya:

import db from '../../db/db.js'

export const getItem = id => {
    try {
        const pet = db?.pets?.filter(pet => pet?.id === id)[0]
        return pet
    } catch (err) {
        console.log('Error', err)
    }
}

export const listItems = () => {
    try {
        return db?.pets
    } catch (err) {
        console.log('Error', err)
    }
}

export const editItem = (id, data) => {
    try {
        const index = db.pets.findIndex(pet => pet.id === id)

        if (index === -1) throw new Error('Pet not found')
        else {
            db.pets[index] = data
            return db.pets[index]
        }        
    } catch (err) {
        console.log('Error', err)
    }
}

export const addItem = data => {
    try {  
        const newPet = { id: db.pets.length + 1, ...data }
        db.pets.push(newPet)
        return newPet

    } catch (err) {
        console.log('Error', err)
    }
}

export const deleteItem = id => {
    try {
        // delete item from db
        const index = db.pets.findIndex(pet => pet.id === id)

        if (index === -1) throw new Error('Pet not found')
        else {
            db.pets.splice(index, 1)
            return db.pets
        }
    } catch (error) {

    }
}

Ini adalah fungsi yang bertanggung jawab untuk berinteraksi dengan lapisan data kami (database) dan mengembalikan informasi yang sesuai ke pengontrol kami.

Basis data

Kami tidak akan menggunakan database nyata untuk contoh ini. Sebagai gantinya, kita hanya akan menggunakan array sederhana yang akan bekerja dengan baik untuk tujuan contoh, meskipun data kita tentu saja akan diatur ulang setiap kali server kita melakukannya.

Di root proyek kita, buat folder db dan file db.js dengan kode ini di dalamnya:

const db = {
    pets: [
        {
            id: 1,
            name: 'Rex',
            type: 'dog',
            age: 3,
            breed: 'labrador',
        },
        {
            id: 2,
            name: 'Fido',
            type: 'dog',
            age: 1,
            breed: 'poodle',
        },
        {
            id: 3,
            name: 'Mittens',
            type: 'cat',
            age: 2,
            breed: 'tabby',
        },
    ]
}

export default db

Seperti yang Anda lihat, objek db kami berisi properti hewan peliharaan yang nilainya adalah array objek, setiap objek menjadi hewan peliharaan. Untuk setiap hewan peliharaan, kami menyimpan id, nama, jenis, usia, dan ras.

Sekarang pergi ke terminal Anda dan jalankan nodemon app.js. Anda akan melihat pesan ini yang mengonfirmasi server Anda hidup: ⚡️ [server]: Server berjalan di [https://localhost:3000](https://localhost:3000).

Cara Menguji REST API dengan Supertest

Sekarang setelah server kita aktif dan berjalan, mari kita terapkan setelan pengujian sederhana untuk memeriksa apakah setiap titik akhir kita berperilaku seperti yang diharapkan.

Jika Anda tidak terbiasa dengan pengujian otomatis, saya sarankan Anda membaca artikel pengantar yang saya tulis beberapa waktu lalu.

Alat kami

SuperTest adalah pustaka JavaScript yang digunakan untuk menguji server HTTP atau aplikasi web yang membuat permintaan HTTP. Ini menyediakan abstraksi tingkat tinggi untuk menguji HTTP, memungkinkan pengembang untuk mengirim permintaan HTTP dan membuat pernyataan tentang respons yang diterima, sehingga lebih mudah untuk menulis pengujian otomatis untuk aplikasi web.

SuperTest bekerja dengan kerangka kerja pengujian JavaScript apa pun, seperti Mocha atau Jest, dan dapat digunakan dengan server HTTP atau kerangka kerja aplikasi web apa pun, seperti Express.

SuperTest dibangun di atas pustaka pengujian populer Mocha, dan menggunakan  pustaka pernyataan Chai untuk membuat pernyataan tentang respons yang diterima. Ini menyediakan API yang mudah digunakan untuk membuat permintaan HTTP, termasuk dukungan untuk autentikasi, header, dan isi permintaan.

SuperTest juga memungkinkan pengembang untuk menguji seluruh siklus permintaan/respons, termasuk middleware dan penanganan kesalahan, menjadikannya alat yang ampuh untuk menguji aplikasi web.

Secara keseluruhan, SuperTest adalah alat yang berharga bagi pengembang yang ingin menulis pengujian otomatis untuk aplikasi web mereka. Ini membantu memastikan bahwa aplikasi mereka berfungsi dengan benar dan bahwa setiap perubahan yang mereka buat pada basis kode tidak menimbulkan bug atau masalah baru.

Kode

Pertama, kita perlu menginstal beberapa dependensi. Untuk menyimpan perintah terminal, buka file package.json Anda dan ganti bagian devDependencies Anda dengan ini. Kemudian jalankan npm install

  "devDependencies": {

    "@babel/core": "^7.21.4",
    "@babel/preset-env": "^7.21.4",
    "babel-jest": "^29.5.0",
    "jest": "^29.5.0",
    "jest-babel": "^1.0.1",
    "nodemon": "^2.0.22",
    "supertest": "^6.3.3"
  }

Di sini kita menginstal perpustakaan supertest dan jest, yang kita butuhkan untuk pengujian kita berjalan, ditambah beberapa hal babel yang kita butuhkan untuk proyek kita untuk mengidentifikasi file mana yang merupakan file uji dengan benar.

Masih dalam package.json Anda, tambahkan skrip ini:

"scripts": {
    "test": "jest"
  },

Untuk mengakhiri dengan boilerplate, di root proyek Anda, buat file babel.config.cjs dan jatuhkan kode ini di dalamnya:

//babel.config.cjs
module.exports = {
    presets: [
      [
        '@babel/preset-env',
        {
          targets: {
            node: 'current',
          },
        },
      ],
    ],
  };

Sekarang mari kita tulis beberapa tes aktual! Di dalam folder rute Anda, buat file pets.test.js dengan kode ini di dalamnya:

import supertest from 'supertest' // Import supertest
import server from '../../app' // Import the server object
const requestWithSupertest = supertest(server) // We will use this function to mock HTTP requests

describe('GET "/"', () => {
    test('GET "/" returns all pets', async () => {
        const res = await requestWithSupertest.get('/pets')
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual([
            {
                id: 1,
                name: 'Rex',
                type: 'dog',
                age: 3,
                breed: 'labrador',
            },
            {
                id: 2,
                name: 'Fido',
                type: 'dog',
                age: 1,
                breed: 'poodle',
            },
            {
                id: 3,
                name: 'Mittens',
                type: 'cat',
                age: 2,
                breed: 'tabby',
            },
        ])
    })
})

describe('GET "/:id"', () => {
    test('GET "/:id" returns given pet', async () => {
        const res = await requestWithSupertest.get('/pets/1')
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual(
            {
                id: 1,
                name: 'Rex',
                type: 'dog',
                age: 3,
                breed: 'labrador',
            }
        )
    })
})

describe('PUT "/:id"', () => {
    test('PUT "/:id" updates pet and returns it', async () => {
        const res = await requestWithSupertest.put('/pets/1').send({
            id: 1,
            name: 'Rexo',
            type: 'dogo',
            age: 4,
            breed: 'doberman'
        })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({
            id: 1,
            name: 'Rexo',
            type: 'dogo',
            age: 4,
            breed: 'doberman'
        })
    })
})

describe('POST "/"', () => {
    test('POST "/" adds new pet and returns the added item', async () => {
        const res = await requestWithSupertest.post('/pets').send({
            name: 'Salame',
            type: 'cat',
            age: 6,
            breed: 'pinky'
        })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({
            id: 4,
            name: 'Salame',
            type: 'cat',
            age: 6,
            breed: 'pinky'
        })
    })
})

describe('DELETE "/:id"', () => {
    test('DELETE "/:id" deletes given pet and returns updated list', async () => {
        const res = await requestWithSupertest.delete('/pets/2')
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual([
            {
                id: 1,
                name: 'Rexo',
                type: 'dogo',
                age: 4,
                breed: 'doberman'
            },
            {
                id: 3,
                name: 'Mittens',
                type: 'cat',
                age: 2,
                breed: 'tabby',
            },
            {
                id: 4,
                name: 'Salame',
                type: 'cat',
                age: 6,
                breed: 'pinky'
            }
        ])
    })
})

Untuk setiap titik akhir, pengujian mengirim permintaan HTTP dan memeriksa respons untuk tiga hal: kode status HTTP, jenis respons (yang harus JSON), dan isi respons (yang harus cocok dengan format JSON yang diharapkan).

  • Pengujian pertama mengirimkan permintaan GET ke titik akhir /pets dan mengharapkan API untuk mengembalikan array hewan peliharaan dalam format JSON.
  • Pengujian kedua mengirimkan permintaan GET ke titik akhir /pets/:id dan mengharapkan API untuk mengembalikan hewan peliharaan dengan ID yang ditentukan dalam format JSON.
  • Pengujian ketiga mengirimkan permintaan PUT ke titik akhir /pets/:id dan mengharapkan API untuk memperbarui hewan peliharaan dengan ID yang ditentukan dan mengembalikan hewan peliharaan yang diperbarui dalam format JSON.
  • Pengujian keempat mengirimkan permintaan POST ke titik akhir /pets dan mengharapkan API untuk menambahkan hewan peliharaan baru dan mengembalikan hewan peliharaan yang ditambahkan dalam format JSON.
  • Terakhir, pengujian kelima mengirimkan permintaan DELETE ke titik akhir /pets/:id dan mengharapkan API untuk menghapus hewan peliharaan dengan ID yang ditentukan dan mengembalikan daftar hewan peliharaan yang diperbarui dalam format JSON.

Setiap pengujian memeriksa apakah kode status HTTP yang diharapkan, jenis respons, dan isi respons dikembalikan. Jika salah satu dari ekspektasi ini tidak terpenuhi, pengujian gagal dan memberikan pesan kesalahan.

Pengujian ini penting untuk memastikan bahwa API bekerja dengan benar dan konsisten di berbagai permintaan dan titik akhir HTTP. Pengujian dapat dijalankan secara otomatis, yang memudahkan untuk mendeteksi masalah atau regresi apa pun dalam fungsionalitas API.

Sekarang pergi ke terminal Anda, jalankan tes npm, dan Anda akan melihat semua tes Anda lulus:

> restapi@1.0.0 test
> jest

 PASS  pets/routes/pets.test.js
  GET "/"
    ✓ GET "/" returns all pets (25 ms)
  GET "/:id"
    ✓ GET "/:id" returns given pet (4 ms)
  PUT "/:id"
    ✓ PUT "/:id" updates pet and returns it (15 ms)
  POST "/"
    ✓ POST "/" adds new pet and returns the added item (3 ms)
  DELETE "/:id"
    ✓ DELETE "/:id" deletes given pet and returns updated list (3 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        1.611 s
Ran all test suites.

Rangkaian Tes: 1 lulus, total 1

Tes: 5 lulus, 5 total

Snapshot: total 0

Waktu: 1.611 detik

Menjalankan semua rangkaian tes.

Cara Mengkonsumsi REST API di Aplikasi React Front-end

Sekarang kita tahu server kita berjalan dan titik akhir kita berperilaku seperti yang diharapkan. Mari kita lihat beberapa contoh yang lebih realistis tentang bagaimana API kita dapat dikonsumsi oleh aplikasi front end.

Untuk contoh ini, kita akan menggunakan aplikasi React, dan dua alat berbeda untuk mengirim dan memproses permintaan kita: Fetch API dan library Axios.

Alat kami

React adalah perpustakaan JavaScript populer untuk membangun antarmuka pengguna. Ini memungkinkan pengembang untuk membuat komponen UI yang dapat digunakan kembali dan memperbarui dan merendernya secara efisien sebagai respons terhadap perubahan status aplikasi.

 Fetch API adalah API browser modern yang memungkinkan pengembang membuat permintaan HTTP asinkron dari kode JavaScript sisi klien. Ini menyediakan antarmuka sederhana untuk mengambil sumber daya di seluruh jaringan, dan mendukung berbagai jenis permintaan dan respons.

Axios adalah pustaka klien HTTP yang populer untuk JavaScript. Ini menyediakan API yang sederhana dan intuitif untuk membuat permintaan HTTP, dan mendukung berbagai fitur, termasuk intersepsi permintaan dan respons, transformasi otomatis untuk data permintaan dan respons, dan kemampuan untuk membatalkan permintaan. Ini dapat digunakan baik di browser maupun di server, dan sering digunakan bersama dengan aplikasi React.

Kode

Mari kita buat aplikasi React kita dengan menjalankan yarn create vite dan mengikuti petunjuk terminal. Setelah selesai, jalankan yarn add axios dan yarn add react-router-dom (yang akan kita gunakan untuk mengatur perutean dasar di aplikasi kita).

Aplikasi.jsx

Masukkan kode ini ke dalam file App.jsx Anda:

import { Suspense, lazy, useState } from 'react'
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'
import './App.css'

const PetList = lazy(() => import ('./pages/PetList'))
const PetDetail = lazy(() => import ('./pages/PetDetail'))
const EditPet = lazy(() => import ('./pages/EditPet'))
const AddPet = lazy(() => import ('./pages/AddPet'))

function App() {

  const [petToEdit, setPetToEdit] = useState(null)

  return (
    <div className="App">
      <Router>
        <h1>Pet shelter</h1>

        <Link to='/add'>
          <button>Add new pet</button>
      </Link>

        <Routes>
          <Route path='/' element={<Suspense fallback={<></>}><PetList /></Suspense>}/>

          <Route path='/:petId' element={<Suspense fallback={<></>}><PetDetail setPetToEdit={setPetToEdit} /></Suspense>}/>

          <Route path='/:petId/edit' element={<Suspense fallback={<></>}><EditPet petToEdit={petToEdit} /></Suspense>}/>

          <Route path='/add' element={<Suspense fallback={<></>}><AddPet /></Suspense>}/>
        </Routes>

      </Router>
    </div>
  )
}

export default App

Di sini kami hanya mendefinisikan rute kami. Kita akan memiliki 4 rute utama di aplikasi kita, masing-masing sesuai dengan tampilan yang berbeda:

  • Satu untuk melihat seluruh daftar hewan peliharaan.
  • Satu untuk melihat detail satu hewan peliharaan.
  • Satu untuk mengedit satu hewan peliharaan.
  • Satu untuk menambahkan hewan peliharaan baru ke daftar.

Selain itu, kami memiliki tombol untuk menambahkan hewan peliharaan baru dan status yang akan menyimpan informasi hewan peliharaan yang ingin kami edit.

Selanjutnya, buat direktori halaman dengan file-file ini di dalamnya:

Citra

Struktur folder

Daftar Hewan Peliharaan.jsx

Mari kita mulai dengan file yang bertanggung jawab untuk merender seluruh daftar hewan peliharaan:

import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'

function PetList() {
    const [pets, setPets] = useState([])

    const getPets = async () => {
        try {
            /* FETCH */
            // const response = await fetch('http://localhost:3000/pets')
            // const data = await response.json()
            // if (response.status === 200) setPets(data)

            /* AXIOS */
            const response = await axios.get('http://localhost:3000/pets')
            if (response.status === 200) setPets(response.data)

        } catch (error) {
            console.error('error', error)
        }
    }

    useEffect(() => { getPets() }, [])

    return (
        <>
            <h2>Pet List</h2>

            {pets?.map((pet) => {
                return (
                    <div key={pet?.id}>
                        <p>{pet?.name} - {pet?.type} - {pet?.breed}</p>

                        <Link to={`/${pet?.id}`}>
                            <button>Pet detail</button>
                        </Link>
                    </div>
                )
            })}
        </>
    )
}

export default PetList

Seperti yang Anda lihat, dari segi logika kami memiliki 3 hal utama di sini:

  • Status yang menyimpan daftar hewan peliharaan yang akan dirender.
  • Fungsi yang mengeksekusi permintaan yang sesuai dengan API kami.
  • useEffect yang menjalankan fungsi tersebut saat komponen dirender.

Anda dapat melihat bahwa sintaks untuk membuat permintaan HTTP dengan fetch dan Axios agak mirip, tetapi Axios sedikit lebih ringkas. Setelah kami membuat permintaan, kami memeriksa apakah statusnya adalah 200 (artinya berhasil), dan menyimpan respons di status kami.

Setelah status kami diperbarui, komponen akan merender data yang disediakan oleh API kami.

Ingatlah bahwa untuk melakukan panggilan ke server kita, kita harus menjalankannya dengan menjalankan nodemon app.js di terminal proyek server kita.

PetDetail.jsx

Sekarang mari kita pergi ke file PetDetail.jsx:

import { useEffect, useState } from 'react'
import { useParams, Link } from 'react-router-dom'
import axios from 'axios'

function PetDetail({ setPetToEdit }) {

    const [pet, setPet] = useState([])

    const { petId } = useParams()

    const getPet = async () => {
        try {
            /* FETCH */
            // const response = await fetch(`http://localhost:3000/pets/${petId}`)
            // const data = await response.json()
            // if (response.status === 200) {
            //     setPet(data)
            //     setPetToEdit(data)
            // }

            /* AXIOS */
            const response = await axios.get(`http://localhost:3000/pets/${petId}`)
            if (response.status === 200) {
                setPet(response.data)
                setPetToEdit(response.data)
            }

        } catch (error) {
            console.error('error', error)
        }
    }

    useEffect(() => { getPet() }, [])

    const deletePet = async () => {
        try {
            /* FETCH */
            // const response = await fetch(`http://localhost:3000/pets/${petId}`, {
            //     method: 'DELETE'
            // })

            /* AXIOS */
            const response = await axios.delete(`http://localhost:3000/pets/${petId}`)

            if (response.status === 200) window.location.href = '/'
        } catch (error) {
            console.error('error', error)
        }
    }

    return (
        <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', aligniItems: 'center' }}>
            <h2>Pet Detail</h2>

            {pet && (
                <>
                    <p>Pet name: {pet.name}</p>
                    <p>Pet type: {pet.type}</p>
                    <p>Pet age: {pet.age}</p>
                    <p>Pet breed: {pet.breed}</p>

                    <div style={{ display: 'flex', justifyContent: 'center', aligniItems: 'center' }}>

                        <Link to={`/${pet?.id}/edit`}>
                            <button style={{ marginRight: 10 }}>Edit pet</button>
                        </Link>

                        <button
                            style={{ marginLeft: 10 }}
                            onClick={() => deletePet()}
                        >
                            Delete pet
                        </button>
                    </div>
                </>
            )}
        </div>
    )
}

export default PetDetail

Di sini kami memiliki dua jenis permintaan yang berbeda:

  • Salah satu yang mendapatkan informasi hewan peliharaan yang diberikan (yang berperilaku sangat mirip dengan permintaan sebelumnya yang kita lihat). Satu-satunya perbedaan di sini adalah kita meneruskan parameter URL ke endpoint kita, yang pada saat yang sama kita baca dari URL di aplikasi front-end kita.
  • Permintaan lainnya adalah menghapus hewan peliharaan yang diberikan dari daftar kami. Perbedaannya di sini adalah setelah kami mengonfirmasi bahwa permintaan berhasil, kami mengarahkan pengguna ke root aplikasi kami.

AddPet.jsx

Ini adalah file yang bertanggung jawab untuk menambahkan hewan peliharaan baru ke daftar kami:

import React, { useState } from 'react'
import axios from 'axios'

function AddPet() {

    const [petName, setPetName] = useState()
    const [petType, setPetType] = useState()
    const [petAge, setPetAge] = useState()
    const [petBreed, setPetBreed] = useState()

    const addPet = async () => {
        try {
            const petData = {
                name: petName,
                type: petType,
                age: petAge,
                breed: petBreed
            }

            /* FETCH */
            // const response = await fetch('http://localhost:3000/pets/', {
            //     method: 'POST',
            //     headers: {
            //         'Content-Type': 'application/json'
            //     },
            //     body: JSON.stringify(petData)
            // })

            // if (response.status === 200) {
            //     const data = await response.json()
            //     window.location.href = `/${data.id}`
            // }

            /* AXIOS */
            const response = await axios.post(
                'http://localhost:3000/pets/',
                petData,
                { headers: { 'Content-Type': 'application/json' } }
            )

            if (response.status === 200) window.location.href = `/${response.data.id}`

        } catch (error) {
            console.error('error', error)
        }
    }

    return (
        <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', aligniItems: 'center' }}>
            <h2>Add Pet</h2>

            <div style={{ display: 'flex', flexDirection: 'column', margin: 20 }}>
                <label>Pet name</label>
                <input type='text' value={petName} onChange={e => setPetName(e.target.value)} />
            </div>

            <div style={{ display: 'flex', flexDirection: 'column', margin: 20 }}>
                <label>Pet type</label>
                <input type='text' value={petType} onChange={e => setPetType(e.target.value)} />
            </div>

            <div style={{ display: 'flex', flexDirection: 'column', margin: 20 }}>
                <label>Pet age</label>
                <input type='text' value={petAge} onChange={e => setPetAge(e.target.value)} />
            </div>

            <div style={{ display: 'flex', flexDirection: 'column', margin: 20 }}>
                <label>Pet breed</label>
                <input type='text' value={petBreed} onChange={e => setPetBreed(e.target.value)} />
            </div>

            <button
                style={{ marginTop: 30 }}
                onClick={() => addPet()}
            >
                Add pet
            </button>
        </div>
    )
}

export default AddPet

Di sini kami merender formulir di mana pengguna harus memasukkan info hewan peliharaan baru.

Kami memiliki status untuk setiap bagian informasi yang akan dimasukkan, dan dalam permintaan kami kami membangun objek dengan setiap status. Objek ini akan menjadi badan permintaan kami.

Atas permintaan kami, kami memeriksa apakah respons berhasil. Jika ya, kita mengarahkan ke halaman detail hewan peliharaan yang baru ditambahkan. Untuk mengalihkan, kami menggunakan id yang dikembalikan dalam respons HTTP. ;)

EditPet.jsx

Terakhir, file yang bertanggung jawab untuk mengedit daftar hewan peliharaan:

import React, { useState } from 'react'
import axios from 'axios'

function EditPet({ petToEdit }) {

    const [petName, setPetName] = useState(petToEdit?.name)
    const [petType, setPetType] = useState(petToEdit?.type)
    const [petAge, setPetAge] = useState(petToEdit?.age)
    const [petBreed, setPetBreed] = useState(petToEdit?.breed)

    const editPet = async () => {
        try {
            const petData = {
                id: petToEdit.id,
                name: petName,
                type: petType,
                age: petAge,
                breed: petBreed
            }

            /* FETCH */
            // const response = await fetch(`http://localhost:3000/pets/${petToEdit.id}`, {
            //     method: 'PUT',
            //     headers: {
            //         'Content-Type': 'application/json'
            //     },
            //     body: JSON.stringify(petData)
            // })

            /* AXIOS */
            const response = await axios.put(
                `http://localhost:3000/pets/${petToEdit.id}`,
                petData,
                { headers: { 'Content-Type': 'application/json' } }
            )

            if (response.status === 200) {
                window.location.href = `/${petToEdit.id}`
            }
        } catch (error) {
            console.error('error', error)
        }
    }

    return (
        <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', aligniItems: 'center' }}>
            <h2>Edit Pet</h2>

            <div style={{ display: 'flex', flexDirection: 'column', margin: 20 }}>
                <label>Pet name</label>
                <input type='text' value={petName} onChange={e => setPetName(e.target.value)} />
            </div>

            <div style={{ display: 'flex', flexDirection: 'column', margin: 20 }}>
                <label>Pet type</label>
                <input type='text' value={petType} onChange={e => setPetType(e.target.value)} />
            </div>

            <div style={{ display: 'flex', flexDirection: 'column', margin: 20 }}>
                <label>Pet age</label>
                <input type='text' value={petAge} onChange={e => setPetAge(e.target.value)} />
            </div>

            <div style={{ display: 'flex', flexDirection: 'column', margin: 20 }}>
                <label>Pet breed</label>
                <input type='text' value={petBreed} onChange={e => setPetBreed(e.target.value)} />
            </div>

            <button
                style={{ marginTop: 30 }}
                onClick={() => editPet()}
            >
                Save changes
            </button>
        </div>
    )
}

export default EditPet

Ini berperilaku sangat mirip dengan file AddPet.jsx. Satu-satunya perbedaan adalah bahwa status info hewan peliharaan kita diinisialisasi dengan nilai hewan peliharaan yang ingin kita edit. Ketika nilai-nilai tersebut diperbarui oleh pengguna, kami membuat objek yang akan menjadi isi permintaan kami dan mengirimkan permintaan dengan informasi yang diperbarui. Cukup mudah. ;)

Dan hanya itu! Kami menggunakan semua titik akhir API kami di aplikasi front end kami. =)

Cara Mendokumentasikan REST API dengan Swagger

Sekarang kami memiliki server kami yang aktif dan berjalan, diuji, dan terhubung ke aplikasi front end kami, langkah terakhir dalam implementasi kami adalah mendokumentasikan API kami.

Mendokumentasikan dan API umumnya berarti mendeklarasikan titik akhir mana yang tersedia, tindakan apa yang dilakukan oleh setiap titik akhir, serta parameter serta nilai pengembalian untuk masing-masing titik akhir.

Ini berguna tidak hanya untuk mengingat cara kerja server kami, tetapi juga bagi orang-orang yang ingin berinteraksi dengan API kami.

Misalnya, di perusahaan sangat biasa memiliki tim back-end dan tim front-end. Ketika API sedang dikembangkan dan perlu diintegrasikan dengan aplikasi front-end, akan sangat membosankan untuk menanyakan titik akhir mana yang melakukan apa, dan parameter apa yang harus dilewati. Jika Anda memiliki semua info itu di satu tempat, Anda bisa pergi ke sana dan membacanya sendiri. Itulah dokumentasinya.

Alat kami

Swagger adalah seperangkat alat sumber terbuka yang membantu pengembang membangun, mendokumentasikan, dan menggunakan layanan web RESTful. Ini menyediakan antarmuka grafis yang ramah pengguna bagi pengguna untuk berinteraksi dengan API dan juga menghasilkan kode klien untuk berbagai bahasa pemrograman untuk mempermudah integrasi API.

Swagger menyediakan serangkaian fitur komprehensif untuk pengembangan API, termasuk desain API, dokumentasi, pengujian, dan pembuatan kode. Ini memungkinkan pengembang untuk menentukan titik akhir API, parameter input, output yang diharapkan, dan persyaratan otentikasi dengan cara standar menggunakan spesifikasi OpenAPI.

Swagger UI adalah alat populer yang merender spesifikasi OpenAPI sebagai dokumentasi API interaktif yang memungkinkan pengembang menjelajahi dan menguji API melalui browser web. Ini menyediakan antarmuka yang ramah pengguna yang memungkinkan pengembang untuk dengan mudah melihat dan berinteraksi dengan titik akhir API.

Cara Menerapkan Swagger

Kembali ke aplikasi server kami, untuk mengimplementasikan Swagger, kami memerlukan dua dependensi baru. Jadi jalankan npm i swagger-jsdoc dan npm i swagger-ui-express.

Selanjutnya, ubah file app.js agar terlihat seperti ini:

import express from 'express'
import cors from 'cors'
import swaggerUI from 'swagger-ui-express'
import swaggerJSdoc from 'swagger-jsdoc'

import petRoutes from './pets/routes/pets.routes.js'

const app = express()
const port = 3000

// swagger definition
const swaggerSpec = {
    definition: {
        openapi: '3.0.0',
        info: {
            title: 'Pets API',
            version: '1.0.0',
        },
        servers: [
            {
                url: `http://localhost:${port}`,
            }
        ]
    },
    apis: ['./pets/routes/*.js'],
}

/* Global middlewares */
app.use(cors())
app.use(express.json())
app.use(
    '/api-docs',
    swaggerUI.serve,
    swaggerUI.setup(swaggerJSdoc(swaggerSpec))
)

/* Routes */
app.use('/pets', petRoutes)

/* Server setup */
if (process.env.NODE_ENV !== 'test') {
    app.listen(port, () => console.log(`⚡️[server]: Server is running at https://localhost:${port}`))
}

export default app

Seperti yang Anda lihat, kami mengimpor dependensi baru, kami membuat objek swaggerSpec yang berisi opsi konfigurasi untuk implementasi kami, dan kemudian mengatur middleware untuk merender dokumentasi kami di direktori /api-docs aplikasi kami.

Sekarang, jika Anda membuka browser dan pergi ke http://localhost:3000/api-docs/ Anda akan melihat ini:

Citra

UI Dokumentasi

Hal yang keren tentang Swagger adalah menyediakan UI out-of-the-box untuk dokumen kami, dan Anda dapat dengan mudah mengaksesnya di jalur URL yang dideklarasikan dalam konfigurasi.

Sekarang mari kita tulis beberapa dokumentasi yang sebenarnya!

Masuk ke file pets.routes.js dan ganti kodenya dengan ini:

import express from "express";
import {
  listPets,
  getPet,
  editPet,
  addPet,
  deletePet,
} from "../controllers/pets.controllers.js";

const router = express.Router();

/**
 * @swagger
 * components:
 *  schemas:
 *     Pet:
 *      type: object
 *      properties:
 *          id:
 *              type: integer
 *              description: Pet id
 *          name:
 *              type: string
 *              description: Pet name
 *          age:
 *              type: integer
 *              description: Pet age
 *          type:
 *              type: string
 *              description: Pet type
 *          breed:
 *              type: string
 *              description: Pet breed
 *     example:
 *          id: 1
 *          name: Rexaurus
 *          age: 3
 *          breed: labrador
 *          type: dog
 */

/**
 * @swagger
 * /pets:
 *  get:
 *     summary: Get all pets
 *     description: Get all pets
 *     responses:
 *      200:
 *         description: Success
 *      500:
 *         description: Internal Server Error
 */
router.get("/", listPets);

/**
 * @swagger
 * /pets/{id}:
 *  get:
 *     summary: Get pet detail
 *     description: Get pet detail
 *     parameters:
 *       - in: path
 *         name: id
 *         schema:
 *           type: integer
 *         required: true
 *         description: Pet id
 *     responses:
 *      200:
 *         description: Success
 *      500:
 *         description: Internal Server Error
 */
router.get("/:id", getPet);

/**
 * @swagger
 * /pets/{id}:
 *  put:
 *     summary: Edit pet
 *     description: Edit pet
 *     parameters:
 *       - in: path
 *         name: id
 *         schema:
 *           type: integer
 *         required: true
 *         description: Pet id
 *     requestBody:
 *       description: A JSON object containing pet information
 *       content:
 *         application/json:
 *           schema:
 *              $ref: '#/components/schemas/Pet'
 *           example:
 *              name: Rexaurus
 *              age: 12
 *              breed: labrador
 *              type: dog
 *     responses:
 *     200:
 *        description: Success
 *     500:
 *       description: Internal Server Error
 *
 */
router.put("/:id", editPet);

/**
 * @swagger
 * /pets:
 *  post:
 *      summary: Add pet
 *      description: Add pet
 *      requestBody:
 *          description: A JSON object containing pet information
 *          content:
 *             application/json:
 *                 schema:
 *                    $ref: '#/components/schemas/Pet'
 *                 example:
 *                    name: Rexaurus
 *                    age: 12
 *                    breed: labrador
 *                    type: dog
 *      responses:
 *      200:
 *          description: Success
 *      500:
 *          description: Internal Server Error
 */
router.post("/", addPet);

/**
 * @swagger
 * /pets/{id}:
 *  delete:
 *     summary: Delete pet
 *     description: Delete pet
 *     parameters:
 *       - in: path
 *         name: id
 *         schema:
 *           type: integer
 *         required: true
 *         description: Pet id
 *     responses:
 *     200:
 *        description: Success
 *     500:
 *       description: Internal Server Error
 */
router.delete("/:id", deletePet);

export default router;

Seperti yang Anda lihat, kami menambahkan jenis komentar khusus untuk setiap titik akhir kami. Ini adalah cara Swagger UI mengidentifikasi dokumentasi dalam kode kami. Kami telah memasukkannya ke dalam file ini karena masuk akal untuk memiliki dokumen sedekat mungkin dengan titik akhir, tetapi Anda dapat menempatkannya di mana pun Anda mau.

Jika kita menganalisis komentar secara rinci, Anda dapat melihat komentar tersebut ditulis dalam sintaks seperti YAML, dan untuk masing-masing komentar kita menentukan rute titik akhir, metode HTTP, deskripsi, parameter yang diterimanya, dan kemungkinan respons.

Semua komentar kurang lebih sama kecuali yang pertama. Dalam hal itu kita mendefinisikan "skema" yang seperti pengetikan ke jenis objek yang nantinya dapat kita gunakan kembali di komentar lain. Dalam kasus kami, kami mendefinisikan skema "Pet" yang kemudian kami gunakan untuk titik akhir put dan post.

Jika Anda memasukkan http://localhost:3000/api-docs/ lagi, Anda sekarang akan melihat ini:

Citra

UI Dokumentasi

Setiap titik akhir dapat diperluas, seperti ini:

Citra

UI Dokumentasi

Dan jika kita mengklik tombol "Coba", kita dapat menjalankan permintaan HTTP dan melihat seperti apa responsnya:

Citra

UI Dokumentasi

Ini sangat berguna untuk pengembang pada umumnya dan orang-orang yang ingin bekerja dengan API kami, dan sangat mudah diatur seperti yang Anda lihat.

Memiliki UI siap pakai menyederhanakan interaksi dengan dokumentasi. Dan memilikinya dalam basis kode kami juga merupakan bonus yang bagus, karena kami dapat memodifikasi dan memperbaruinya tanpa perlu menyentuh hal lain selain kode kami sendiri.

 

Komentar