โ˜€๏ธSiang
Web Development

Node.js & Express: Membuat Web Server

Tutorial lengkap membuat web server dan REST API menggunakan Node.js dan Express.js โ€” dari konsep dasar hingga middleware, routing, template engine, dan error handling

Artikel: Nodejs Express Artikel: Nodejs Express


1. Pengenalan Node.js

Node.js adalah runtime environment JavaScript yang berjalan di luar browser โ€” di server, desktop, atau perangkat IoT. Dikembangkan oleh Ryan Dahl pada tahun 2009, Node.js memungkinkan pengembang menggunakan JavaScript untuk membangun backend, API, dan aplikasi jaringan yang scalable.

Node.js dibangun di atas mesin JavaScript V8 dari Google Chrome dan menggunakan model event-driven, non-blocking I/O yang membuatnya sangat efisien untuk menangani banyak koneksi secara bersamaan. Ini menjadikan Node.js pilihan ideal untuk aplikasi real-time seperti chat, streaming, dan API berperforma tinggi.

Mengapa Memilih Node.js?

Keunggulan Penjelasan
Non-blocking I/OMenangani ribuan koneksi secara bersamaan tanpa blocking thread
Full-stack JavaScriptBahasa yang sama untuk frontend dan backend โ€” mempercepat pengembangan
NPM EcosystemLebih dari 2 juta package siap pakai di npm registry
Event-drivenCocok untuk aplikasi real-time seperti chat, notifikasi, dan streaming
Performa TinggiV8 engine mengkompilasi JavaScript ke native machine code
Komunitas BesarDukungan luas dari perusahaan seperti Netflix, PayPal, LinkedIn

Arsitektur Node.js

Diagram: Node.js Architecture

Client HTTP Request

Event Loop Single Thread

Callback Queue

V8 JavaScript Engine JS โ†’ Native Machine Code (...

C++ Thread Pool File I/O ยท DNS ยท Crypto libuv (...

Response (Server)

โ–ธ Event Loop: Menangani request synchronous sec...

Instalasi Node.js

Unduh Node.js dari situs resmi nodejs.org atau gunakan nvm (Node Version Manager) untuk mengelola beberapa versi:

Diagram

Request (Client)

Middleware 1 (Logger)

Middleware 2 (Auth)

Route Handler (Controller)

Response (Server)

Middleware 3 (CORS)

Middleware N (Error Handler)

Membuat dan Menggunakan Middleware

JavaScript โ€” Middleware
// 1. Custom Logger Middleware
const logger = (req, res, next) => {
  const waktu = new Date().toISOString();
  console.log(`[${waktu}] ${req.method} ${req.url}`);
  next(); // Lanjutkan ke middleware/route berikutnya
};
app.use(logger);

// 2. Auth Middleware
const autentikasi = (req, res, next) => {
  const token = req.headers['authorization'];
  if (!token || token !== 'Bearer my-secret-token') {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Token tidak valid atau tidak ditemukan'
    });
  }
  req.user = { id: 1, role: 'admin' }; // Attach user ke request
  next();
};

// Menggunakan middleware hanya untuk route tertentu
app.get('/admin/dashboard', autentikasi, (req, res) => {
  res.json({
    message: `Selamat datang, ${req.user.role}!`,
    data: { stats: 'Semua data admin di sini' }
  });
});

// 3. Rate Limiting Middleware (sederhana)
const rateLimit = {};
const rateLimiter = (req, res, next) => {
  const ip = req.ip;
  const now = Date.now();

  if (!rateLimit[ip]) {
    rateLimit[ip] = { count: 1, firstRequest: now };
  } else if (now - rateLimit[ip].firstRequest < 60000) {
    rateLimit[ip].count++;
    if (rateLimit[ip].count > 100) {
      return res.status(429).json({
        error: 'Terlalu banyak request. Coba lagi dalam 1 menit.'
      });
    }
  } else {
    rateLimit[ip] = { count: 1, firstRequest: now };
  }
  next();
};
app.use(rateLimiter);

// 4. Middleware untuk parsing request body
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// 5. CORS Middleware
const cors = require('cors');
app.use(cors({
  origin: ['http://localhost:5173', 'https://beebanelabs.pages.dev'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true
}));

// 6. Security Middleware
const helmet = require('helmet');
app.use(helmet());

// 7. HTTP Request Logger
const morgan = require('morgan');
app.use(morgan('combined'));
๐Ÿ’ก Middleware Bawaan vs Pihak Ketiga


6. Template Engine (EJS)

Template engine memungkinkan Anda membuat halaman HTML dinamis dengan menyisipkan data dari server ke dalam template. Salah satu template engine paling populer untuk Express adalah EJS (Embedded JavaScript) karena sintaksnya yang langsung menggunakan JavaScript biasa di dalam HTML.

Setup EJS

Bash
# Instal EJS
npm install ejs

# Buat folder views
mkdir views
JavaScript โ€” Konfigurasi EJS
// Konfigurasi EJS di Express
app.set('view engine', 'ejs');
app.set('views', './views');

// Route yang me-render template
app.get('/', (req, res) => {
  res.render('index', {
    judul: 'BeebaneLabs',
    pesan: 'Selamat datang di tutorial Express!',
    menu: ['Beranda', 'Tutorial', 'Kontak']
  });
});

app.get('/produk', (req, res) => {
  const daftarProduk = [
    { nama: 'Laptop ASUS', harga: 12000000, stok: 5 },
    { nama: 'Mouse Logitech', harga: 250000, stok: 20 },
    { nama: 'Keyboard Mekanik', harga: 800000, stok: 12 },
  ];

  res.render('produk', {
    judul: 'Daftar Produk',
    produk: daftarProduk
  });
});

Template EJS

HTML โ€” views/index.ejs
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <title><%= judul %></title>
</head>
<body>
  <nav>
    <ul>
      <% menu.forEach(item => { %>
        <li><a href="#"><%= item %></a></li>
      <% }); %>
    </ul>
  </nav>

  <main>
    <h1><%= judul %></h1>
    <p><%= pesan %></p>
  </main>

  <%- include('partials/footer') %>
</body>
</html>
HTML โ€” views/produk.ejs
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <title><%= judul %></title>
</head>
<body>
  <h1><%= judul %></h1>

  <% if (produk.length === 0) { %>
    <p>Tidak ada produk tersedia.</p>
  <% } else { %>
    <table border="1">
      <thead>
        <tr>
          <th>Nama</th>
          <th>Harga</th>
          <th>Stok</th>
        </tr>
      </thead>
      <tbody>
        <% produk.forEach(item => { %>
          <tr>
            <td><%= item.nama %></td>
            <td>Rp <%= item.harga.toLocaleString('id-ID') %></td>
            <td><%= item.stok %></td>
          </tr>
        <% }); %>
      </tbody>
    </table>
  <% } %>
</body>
</html>


7. Membangun REST API

Mari kita bangun REST API lengkap untuk mengelola data artikel. API ini mendukung operasi CRUD (Create, Read, Update, Delete) dengan data disimpan di memory (array). Untuk production, Anda bisa menggantinya dengan database seperti MongoDB atau PostgreSQL.

JavaScript โ€” REST API Lengkap
const express = require('express');
const app = express();
app.use(express.json());

// Simpan data di memory
let artikel = [
  { id: 1, judul: 'Belajar Node.js', penulis: 'Budi', tanggal: '2026-06-01' },
  { id: 2, judul: 'Tutorial Express', penulis: 'Sari', tanggal: '2026-06-15' },
  { id: 3, judul: 'REST API Design', penulis: 'Andi', tanggal: '2026-06-20' },
];

// GET semua artikel (dengan filter & pagination)
app.get('/api/artikel', (req, res) => {
  let hasil = [...artikel];

  // Filter berdasarkan penulis
  if (req.query.penulis) {
    hasil = hasil.filter(a =>
      a.penulis.toLowerCase().includes(req.query.penulis.toLowerCase())
    );
  }

  // Pagination
  const halaman = parseInt(req.query.halaman) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const mulai = (halaman - 1) * limit;
  const totalArtikel = hasil.length;
  hasil = hasil.slice(mulai, mulai + limit);

  res.json({
    status: 'success',
    data: hasil,
    pagination: {
      halaman: halaman,
      limit: limit,
      total: totalArtikel,
      totalPages: Math.ceil(totalArtikel / limit)
    }
  });
});

// GET satu artikel berdasarkan ID
app.get('/api/artikel/:id', (req, res) => {
  const item = artikel.find(a => a.id === parseInt(req.params.id));
  if (!item) {
    return res.status(404).json({
      status: 'error',
      message: 'Artikel tidak ditemukan'
    });
  }
  res.json({ status: 'success', data: item });
});

// POST artikel baru
app.post('/api/artikel', (req, res) => {
  const { judul, penulis } = req.body;

  // Validasi
  if (!judul || !penulis) {
    return res.status(400).json({
      status: 'error',
      message: 'Judul dan penulis wajib diisi'
    });
  }

  const newItem = {
    id: artikel.length > 0 ? Math.max(...artikel.map(a => a.id)) + 1 : 1,
    judul,
    penulis,
    tanggal: new Date().toISOString().split('T')[0]
  };

  artikel.push(newItem);
  res.status(201).json({
    status: 'success',
    message: 'Artikel berhasil dibuat',
    data: newItem
  });
});

// PUT memperbarui artikel
app.put('/api/artikel/:id', (req, res) => {
  const index = artikel.findIndex(a => a.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({
      status: 'error',
      message: 'Artikel tidak ditemukan'
    });
  }

  const { judul, penulis } = req.body;
  artikel[index] = {
    ...artikel[index],
    judul: judul || artikel[index].judul,
    penulis: penulis || artikel[index].penulis
  };

  res.json({
    status: 'success',
    message: 'Artikel berhasil diperbarui',
    data: artikel[index]
  });
});

// DELETE artikel
app.delete('/api/artikel/:id', (req, res) => {
  const index = artikel.findIndex(a => a.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({
      status: 'error',
      message: 'Artikel tidak ditemukan'
    });
  }

  const deleted = artikel.splice(index, 1);
  res.json({
    status: 'success',
    message: 'Artikel berhasil dihapus',
    data: deleted[0]
  });
});

app.listen(3000, () => console.log('API berjalan di http://localhost:3000'));


8. Error Handling

Error handling yang baik sangat penting untuk web server production. Express memiliki mekanisme khusus untuk menangkap error melalui error-handling middleware โ€” middleware dengan 4 parameter: (err, req, res, next).

JavaScript โ€” Error Handling
// Custom Error class
class AppError extends Error {
  constructor(pesan, statusCode) {
    super(pesan);
    this.statusCode = statusCode;
    this.status = statusCode < 500 ? 'fail' : 'error';
    this.isOperational = true;
  }
}

// Async wrapper untuk menangkap error di async route
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Menggunakan asyncHandler pada route
app.get('/api/data/:id', asyncHandler(async (req, res) => {
  const data = await ambilDataDariDB(req.params.id);

  if (!data) {
    throw new AppError('Data tidak ditemukan', 404);
  }

  res.json({ status: 'success', data });
}));

// Contoh route dengan error
app.get('/api/test-error', (req, res, next) => {
  // Cara 1: Menggunakan next(error)
  const err = new AppError('Contoh error sengaja', 400);
  next(err);
});

// Error-handling middleware (HARUS 4 parameter!)
app.use((err, req, res, next) => {
  const statusCode = err.statusCode || 500;
  const status = err.status || 'error';

  // Log error di server
  console.error(`[ERROR] ${new Date().toISOString()}`);
  console.error(`  Status: ${statusCode}`);
  console.error(`  Pesan: ${err.message}`);
  console.error(`  Stack: ${err.stack}`);

  // Kirim response ke client
  res.status(statusCode).json({
    status: status,
    message: err.isOperational ? err.message : 'Terjadi kesalahan internal',
    ...(process.env.NODE_ENV === 'development' && {
      stack: err.stack,
      detail: err.message
    })
  });
});

// Handle 404 โ€” route tidak ditemukan
app.use((req, res) => {
  res.status(404).json({
    status: 'error',
    message: `Endpoint ${req.method} ${req.url} tidak ditemukan`
  });
});
โš ๏ธ Jangan Tampilkan Stack Trace di Production!

Di production, jangan pernah mengirim err.stack ke client. Cukup tampilkan pesan error yang ramah dan log detail error di server. Gunakan environment variable NODE_ENV=production untuk membedakan mode development dan production.



9. Serving Static Files

Express dapat melayani file statis seperti HTML, CSS, JavaScript, gambar, dan font menggunakan middleware express.static(). Ini sangat berguna untuk menyajikan frontend atau asset bersamaan dengan API.

JavaScript โ€” Static Files
const path = require('path');

// Setup folder statis
// Semua file di folder 'public' bisa diakses langsung
app.use(express.static(path.join(__dirname, 'public')));

// Bisa juga menambahkan prefix path
app.use('/assets', express.static(path.join(__dirname, 'assets')));

// Beberapa folder statis sekaligus
app.use(express.static('public'));
app.use(express.static('uploads'));

// Dengan opsi caching untuk performa
app.use(express.static('public', {
  maxAge: '1d',         // Cache selama 1 hari
  etag: true,           // Gunakan ETag
  lastModified: true    // Kirim Last-Modified header
}));

// Contoh struktur folder:
// my-server/
// โ”œโ”€โ”€ public/
// โ”‚   โ”œโ”€โ”€ css/
// โ”‚   โ”‚   โ””โ”€โ”€ style.css
// โ”‚   โ”œโ”€โ”€ js/
// โ”‚   โ”‚   โ””โ”€โ”€ app.js
// โ”‚   โ”œโ”€โ”€ images/
// โ”‚   โ”‚   โ””โ”€โ”€ logo.png
// โ”‚   โ””โ”€โ”€ index.html
// โ”œโ”€โ”€ assets/
// โ”‚   โ”œโ”€โ”€ fonts/
// โ”‚   โ””โ”€โ”€ icons/
// โ””โ”€โ”€ index.js

// Mengakses file statis:
// http://localhost:3000/css/style.css
// http://localhost:3000/js/app.js
// http://localhost:3000/images/logo.png
// http://localhost:3000/index.html

// Mengakses file di /assets:
// http://localhost:3000/assets/fonts/myfont.woff2

Serving HTML Page untuk SPA

JavaScript โ€” SPA Support
// Untuk Single Page Application (React, Vue, dll.)
// Semua route yang tidak cocok dengan API akan mengirim index.html
app.use(express.static(path.join(__dirname, 'client/build')));

// Fallback ke index.html untuk client-side routing
app.get('*', (req, res) => {
  // Hanya untuk route non-API
  if (!req.url.startsWith('/api')) {
    res.sendFile(path.join(__dirname, 'client/build', 'index.html'));
  }
});


10. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Node.js dan Express:

Pertanyaan 1: Apa yang dimaksud dengan "non-blocking I/O" di Node.js?

a) Node.js tidak bisa membaca file
b) Node.js mengeksekusi operasi I/O secara asynchronous tanpa memblokir thread utama
c) Node.js hanya bisa menangani satu request sekaligus
d) Node.js membutuhkan database untuk semua operasi

Pertanyaan 2: Perintah apa yang digunakan untuk menginstal Express.js?

a) node install express
b) npm install express
c) npm add express module
d) node get express

Pertanyaan 3: Apa fungsi dari next() dalam middleware Express?

a) Mengakhiri request dan mengirim response
b) Melewati middleware saat ini dan melanjutkan ke middleware/route berikutnya
c) Mengulang middleware dari awal
d) Menghapus request dari queue

Pertanyaan 4: HTTP status code mana yang tepat ketika resource baru berhasil dibuat?

a) 200 OK
b) 201 Created
c) 204 No Content
d) 301 Moved Permanently

Pertanyaan 5: Apa perbedaan antara dependencies dan devDependencies di package.json?

a) Tidak ada perbedaan
b) Dependencies dibutuhkan saat production, devDependencies hanya selama pengembangan
c) Dependencies untuk Node.js, devDependencies untuk browser
d) Dependencies lebih ringan, devDependencies lebih berat
๐Ÿ” Zoom
100%
๐ŸŽจ Tema