Membuat REST API dengan Express.js dan Prisma ORM: CRUD Operations Step-by-Step

Lhuqita Fazry
Web Development Express.js Prisma ORM REST API
Membuat REST API dengan Express.js dan Prisma ORM: CRUD Operations Step-by-Step

Membangun REST API yang terstruktur membutuhkan kombinasi framework yang tepat dan ORM yang efisien. Express.js menangani routing dan middleware, sementara Prisma ORM mengelola koneksi database dan query dengan type-safe schema definition. Kombinasi keduanya menghasilkan API yang maintainable dan produktif untuk dikembangkan.

Keunggulan utama Prisma dibandingkan ORM tradisional seperti Sequelize atau TypeORM terletak pada dx (developer experience) yang superior. Schema deklaratif menggantikan model definition yang verbose, migrasi otomatis mengurangi boilerplate, dan Prisma Client menghasilkan TypeScript types secara otomatis berdasarkan schema yang didefinisikan. Error yang sering muncul saat runtime — seperti typo pada nama kolom — tertangkap saat compile time.

Setup Project dan Instalasi

Mulai dengan membuat project Node.js dan menginstall dependencies yang diperlukan. Prisma membutuhkan inisialisasi khusus untuk menghasilkan schema dan client.

bashbash
# Inisialisasi project Node.js
mkdir rest-api-prisma && cd rest-api-prisma
npm init -y

# Install dependencies
npm install express @prisma/client
npm install -D prisma ts-node typescript @types/express @types/node

# Inisialisasi Prisma
npx prisma init --datasource-provider sqlite

File prisma/schema.prisma akan terbentuk otomatis setelah inisialisasi. Perintah ini juga membuat file .env yang berisi konfigurasi database URL. Untuk project awal, SQLite adalah pilihan tepat karena tidak memerlukan server database terpisah.

Mendefinisikan Prisma Schema

Schema di Prisma mendefinisikan model data dan relasi antar tabel. Pendekatan deklaratif ini menggantikan penulisan SQL manual dan menghasilkan type-safe client secara otomatis.

prismaprisma
// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

Relasi antara User dan Post menggunakan field authorId sebagai foreign key. Prisma secara otomatis mengelola relasi ini — setiap query yang melibatkan author pada model Post akan menghasilkan JOIN yang sesuai tanpa menuliskan SQL secara manual.

Setelah mendefinisikan schema, jalankan migrasi untuk membuat tabel di database:

bashbash
npx prisma migrate dev --name init
npx prisma generate
MERN Stack Development
Web App • Beginner

MERN Stack Development

Launch your journey into full-stack web development with this comprehensive, pro...

Daftar

Perintah migrate dev membuat file migrasi dan menerapkannya ke database. Perintah generate menghasilkan Prisma Client yang akan kita import di aplikasi Express.

Mengimplementasikan Express Server

Dengan schema dan Prisma Client yang siap, kita dapat membangun Express server dengan endpoint CRUD. Struktur yang akan kita buat mengikuti pola RESTful: GET untuk membaca, POST untuk membuat, PUT untuk memperbarui, dan DELETE untuk menghapus.

javascriptjavascript
const express = require('express');
const { PrismaClient } = require('@prisma/client');

const prisma = new PrismaClient();
const app = express();
const PORT = 3000;

app.use(express.json());

// GET /users - Ambil semua user
app.get('/users', async (req, res) => {
  const users = await prisma.user.findMany({
    include: { posts: true }
  });
  res.json(users);
});

// GET /users/:id - Ambil user berdasarkan ID
app.get('/users/:id', async (req, res) => {
  const user = await prisma.user.findUnique({
    where: { id: parseInt(req.params.id) },
    include: { posts: true }
  });
  if (!user) return res.status(404).json({ error: 'User tidak ditemukan' });
  res.json(user);
});

// POST /users - Buat user baru
app.post('/users', async (req, res) => {
  const { email, name } = req.body;
  try {
    const user = await prisma.user.create({
      data: { email, name }
    });
    res.status(201).json(user);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

app.listen(PORT, () => {
  console.log(`Server berjalan di port ${PORT}`);
});

Setiap endpoint menggunakan Prisma Client method yang merepresentasikan operasi database. Method findMany mengambil semua record, findUnique mencari record spesifik, dan create menambahkan record baru. Parameter include: { posts: true } memuat relasi posts bersamaan dengan user dalam satu query, menghindari N+1 query problem.

Operasi CRUD untuk Posts

Endpoint untuk resource Post mengikuti pola yang sama, dengan tambahan validasi dan error handling yang lebih lengkap.

javascriptjavascript
// GET /posts - Ambil semua post yang sudah dipublikasi
app.get('/posts', async (req, res) => {
  const posts = await prisma.post.findMany({
    where: { published: true },
    include: { author: true },
    orderBy: { createdAt: 'desc' }
  });
  res.json(posts);
});

// POST /posts - Buat post baru
app.post('/posts', async (req, res) => {
  const { title, content, authorId } = req.body;
  try {
    const post = await prisma.post.create({
      data: {
        title,
        content,
        authorId,
      }
    });
    res.status(201).json(post);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// PUT /posts/:id - Update post
app.put('/posts/:id', async (req, res) => {
  const { title, content, published } = req.body;
  try {
    const post = await prisma.post.update({
      where: { id: parseInt(req.params.id) },
      data: { title, content, published }
    });
    res.json(post);
  } catch (error) {
    res.status(404).json({ error: 'Post tidak ditemukan' });
  }
});

// DELETE /posts/:id - Hapus post
app.delete('/posts/:id', async (req, res) => {
  try {
    await prisma.post.delete({
      where: { id: parseInt(req.params.id) }
    });
    res.status(204).send();
  } catch (error) {
    res.status(404).json({ error: 'Post tidak ditemukan' });
  }
});

Endpoint GET /posts hanya menampilkan post yang sudah dipublikasi menggunakan filter where: { published: true }. Endpoint PUT menggunakan method update yang menggabungkan pencarian dan perubahan dalam satu operasi — Prisma menghasilkan query UPDATE yang efisien. Endpoint DELETE mengembalikan status 204 (No Content) sesuai konvensi REST.

Perlu diperhatikan bahwa operasi update dan delete di Prisma akan melempar error jika record dengan where clause tidak ditemukan. Error handler di blok catch menangkap kasus ini dan mengembalikan respons 404 yang sesuai. Pendekatan ini lebih eksplisit dibandingkan mengandalkan return value null dari method findUnique.

Error Handling dan Validation

Penanganan error yang konsisten merupakan fondasi API yang reliable. Express middleware memungkinkan pemrosesan error secara terpusat tanpa menulis try-catch di setiap handler.

javascriptjavascript
// Middleware untuk async error handling
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Error handler global
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Terjadi kesalahan pada server',
    message: process.env.NODE_ENV === 'development' ? err.message : undefined
  });
});

// Gunakan asyncHandler di setiap route
app.get('/users', asyncHandler(async (req, res) => {
  const users = await prisma.user.findMany();
  res.json(users);
}));

Wrapper asyncHandler mengeliminasi pengulangan try-catch di setiap endpoint. Ketika error terjadi di dalam async handler, error tersebut secara otomatis diteruskan ke middleware error handler global. Di environment development, pesan error detail ditampilkan untuk memudahkan debugging, sementara di produksi hanya pesan generik yang dikembalikan ke client.

Prisma juga menyediakan error class spesifik seperti PrismaClientKnownRequestError yang dapat digunakan untuk membedakan jenis error — misalnya, P2002 untuk unique constraint violation dan P2025 untuk record not found. Menambahkan penanganan khusus untuk error-error ini membuat respons API lebih informatif dan dapat ditangani oleh client dengan tepat.

Prisma Studio dan Database Explorer

Salah satu fitur unggul Prisma yang sering terlewatkan adalah Prisma Studio — visual editor untuk database yang dapat diakses langsung dari terminal. Jalankan perintah npx prisma studio dan browser akan terbuka dengan interface untuk melihat, menambah, mengedit, dan menghapus record dari setiap tabel.

Prisma Studio berguna selama tahap development untuk memverifikasi data tanpa menulis query SQL secara manual. Kita dapat memastikan bahwa endpoint POST benar-benar menyimpan data ke database, atau mengecek apakah relasi antara User dan Post terbentuk dengan benar setelah operasi create. Fitur ini menggantikan kebutuhan akan database GUI terpisah seperti DB Browser for SQLite atau pgAdmin.

Mau memperdalam skill Web Development secara sistematis? Bergabunglah dengan Web Development Bootcamp di Rumah Coding. Kurikulum praktis dengan proyek real-world dan mentorship dari praktisi industri.

Course Terkait

EduStream - Mini Learning Management System (LMS)
Premium Course Web App

MERN Stack Development

Launch your journey into full-stack web development with this comprehensive, project-driven course. Designed for beginners, this course demystifies the MERN stack (MongoDB, Express.js, React.js, Node.js) by guiding you step-by-step in building a real-world application from scratch. By the end of this course, you will have the practical skills and a complete portfolio project to confidently step into the modern web development industry.

Capstone Project

EduStream - Mini Learning Management System (LMS)

  • Secure Authentication & Authorization: Robust user registration and login using JWT, with strict role-based access control (Admin/Instructor vs. Student).
  • Course Management (Admin Dashboard): Full CRUD (Create, Read, Update, Delete) capabilities for administrators to manage course details, including titles, descriptions, pricing, and thumbnail image uploads.
  • Public Course Catalog: An interactive and responsive storefront where users can browse available courses.
7 Weeks Beginner
Lihat Detail Course

Artikel Terkait