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/O | Menangani ribuan koneksi secara bersamaan tanpa blocking thread |
| Full-stack JavaScript | Bahasa yang sama untuk frontend dan backend โ mempercepat pengembangan |
| NPM Ecosystem | Lebih dari 2 juta package siap pakai di npm registry |
| Event-driven | Cocok untuk aplikasi real-time seperti chat, notifikasi, dan streaming |
| Performa Tinggi | V8 engine mengkompilasi JavaScript ke native machine code |
| Komunitas Besar | Dukungan luas dari perusahaan seperti Netflix, PayPal, LinkedIn |
Arsitektur Node.js
Instalasi Node.js
Unduh Node.js dari situs resmi nodejs.org atau gunakan nvm (Node Version Manager) untuk mengelola beberapa versi:
Membuat dan Menggunakan 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'));
- Bawaan Express:
express.json(),express.urlencoded(),express.static() - Pihak ketiga:
cors,helmet,morgan,express-validator,multer - Custom: Buat sendiri sesuai kebutuhan โ sangat fleksibel!
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
# Instal EJS
npm install ejs
# Buat folder views
mkdir views
// 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
<!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>
<!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.
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).
// 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`
});
});
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.
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
// 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: