Tutorial Deployment Proyek Backend Node.js ke Vercel: Dari Serverless Function sampai Troubleshooting

Tutorial
Aug 8, 2026
14 mnt baca
54 tayangan
Tutorial Deployment Proyek Backend Node.js ke Vercel: Dari Serverless Function sampai Troubleshooting
A

Admin

Penulis Artikel

Tutorial Deployment Proyek Backend Node.js ke Vercel: Dari Serverless Function sampai Troubleshooting

Di artikel sebelumnya, Todo List API kita sudah tersimpan aman dan online di GitHub. Tapi repository di GitHub itu masih berupa kode — belum bisa diakses lewat URL publik seperti API sungguhan yang dipakai aplikasi mobile atau web di dunia nyata. Artikel ini adalah kelanjutan sekaligus penutup tambahan dari seri “Backend Node.js untuk Pemula”: membuat Todo List API kita benar-benar live lewat Vercel, platform hosting yang populer, gratis untuk project skala kecil-menengah, dan terintegrasi langsung dengan GitHub.

Artikel ini akan membahas cara menyesuaikan project Express agar kompatibel dengan Vercel Serverless Functions, langkah menghubungkan repository ke dashboard Vercel, sampai tiga masalah paling umum yang biasa ditemui peserta saat deploy pertama kali — lengkap dengan penyebab dan solusinya.

Artikel ini melanjutkan seluruh seri sebelumnya: Restrukturisasi CRUD, Middleware & .env, Error Handling, Autentikasi JWT, API Key, Validasi & Pagination, Automated Testing, Postman & Swagger, Struktur Boilerplate, README.md, dan Push ke GitHub.


1. Apa Itu Serverless dan Kenapa Express Perlu Disesuaikan?

Analogi: sepanjang seri ini, server yang kita bangun lewat app.listen() di server.js itu ibarat satpam yang berdiri di pos gerbang sekolah dari pagi sampai malam, terus-menerus, siap menerima tamu kapan saja — ini disebut model traditional server. Vercel bekerja dengan model berbeda yang disebut serverless: ibarat petugas piket yang baru datang begitu ada bel dibunyikan (ada request masuk), menyelesaikan tugasnya, lalu pulang lagi sampai bel berikutnya berbunyi. Tidak ada yang “berdiri terus-menerus” menunggu di gerbang.

Penjelasan teknis: konsekuensinya, fungsi app.listen(PORT, ...) yang kita tulis di src/server.js sejak artikel pertama tidak relevan lagi di lingkungan Vercel — platform ini tidak butuh proses yang terus mendengarkan port tertentu. Sebagai gantinya, Vercel butuh sebuah handler function yang bisa dipanggil setiap kali ada request masuk. Untungnya, karena kita sudah memisahkan app.js (konfigurasi Express murni) dari server.js (yang memanggil listen()) sejak artikel pertama, penyesuaian ini jadi jauh lebih mudah — kita tidak perlu mengubah satu pun baris di dalam app.js.


2. Persiapan Sebelum Deploy

Pastikan tiga hal berikut sudah terpenuhi sebelum lanjut:

  • Project sudah ter-push ke GitHub, mengikuti langkah di artikel sebelumnya.

  • Database MongoDB sudah memakai MongoDB Atlas (cloud), bukan mongodb://127.0.0.1:27017 yang cuma bisa diakses dari komputer lokal — server Vercel tidak bisa menjangkau database yang hanya berjalan di laptopmu. Kalau belum, buat cluster gratis di MongoDB Atlas dan ambil connection string-nya untuk dipakai di langkah berikutnya.

  • Punya akun Vercel — cukup buka vercel.com, klik Sign Up, lalu pilih Continue with GitHub. Karena kita login pakai akun GitHub yang sama dengan tempat repository kita berada, Vercel otomatis bisa membaca daftar repository kita tanpa perlu setup token atau SSH tambahan.

2.1 Mengatur Network Access di MongoDB Atlas (IP Whitelist)

Analogi: anggap MongoDB Atlas punya daftar tamu di pos satpam — hanya alamat IP yang namanya ada di daftar itu yang diizinkan masuk ke database. Di komputer lokal, ini gampang: kamu tinggal daftarkan IP rumahmu satu kali (biasanya lewat tombol “Add Current IP Address”), dan IP itu relatif tetap. Masalahnya, server Vercel bekerja dengan model serverless yang kita bahas di bagian 1 — setiap kali function dijalankan, ia bisa “dilayani” oleh mesin fisik yang berbeda-beda dengan alamat IP yang ikut berubah-ubah. Kalau kamu cuma mendaftarkan satu IP tetap seperti biasa, koneksi dari Vercel akan ditolak karena IP-nya tidak pernah cocok dengan yang terdaftar.

Solusi: di dashboard MongoDB Atlas, buka menu Network Access, klik Add IP Address, lalu pilih opsi Allow Access from Anywhere (atau masukkan manual 0.0.0.0/0).

IP Address: 0.0.0.0/0
Comment: Allow access from Vercel (dynamic IP)

Penjelasan teknis: 0.0.0.0/0 adalah notasi CIDR yang berarti “seluruh kemungkinan alamat IP di internet” — jadi bukan mendaftarkan satu IP Vercel yang spesifik (karena memang tidak ada satu IP tetap untuk didaftarkan), melainkan membuka akses dari IP mana pun. Ini adalah pendekatan standar yang direkomendasikan MongoDB Atlas sendiri untuk platform hosting serverless/dynamic seperti Vercel, Railway, atau Render.

Catatan keamanan: membuka akses dari 0.0.0.0/0 bukan berarti databasemu jadi tidak aman sama sekali — siapa pun yang mencoba konek tetap wajib melewati autentikasi username dan password yang tercantum di dalam MONGODB_URI. Lapisan keamanan sesungguhnya ada di kredensial itu (yang wajib dirahasiakan lewat .env sejak artikel kedua), bukan di daftar IP. Untuk project skala belajar dan sebagian besar project production skala kecil-menengah yang di-hosting di platform serverless, praktik ini sudah lazim dan dianggap cukup aman.


3. Membuat Entry Point Serverless

Analogi: kalau server.js dulunya berperan sebagai satpam yang berdiri di gerbang, file baru yang akan kita buat ini berperan seperti resepsionis yang dipanggil setiap kali ada tamu datang — dia tidak berdiri menunggu, tapi selalu siap merespons begitu “dipanggil” oleh Vercel.

Buat folder dan file baru di root project (sejajar dengan folder src/):

mkdir -p api

api/index.js

require("dotenv").config();
const app = require("../src/app");
const connectDB = require("../src/config/db");

// Hubungkan ke MongoDB
connectDB();

// Export aplikasi Express sebagai Vercel Serverless Function
module.exports = app;

Penjelasan teknis:

  • File ini hampir mirip src/server.js yang sudah kita buat sejak artikel pertama — sama-sama memuat .env dan menyambungkan database. Bedanya di baris paling akhir: alih-alih memanggil app.listen(PORT, ...), kita cukup module.exports = app. Vercel yang akan mengurus bagian “mendengarkan request”-nya secara otomatis di baliknya, kita cukup menyerahkan objek app Express yang sudah lengkap dengan seluruh middleware dan route dari artikel-artikel sebelumnya.

  • Karena app.js tidak diubah sedikit pun, seluruh middleware (logger, error handler dari artikel kedua), route auth & todo, dan konfigurasi Swagger dari artikel kedelapan otomatis ikut terbawa tanpa perlu ditulis ulang.

  • Catatan lanjutan (opsional): memanggil connectDB() di setiap eksekusi function bisa membuat koneksi baru dibuat berulang kali saat traffic tinggi, karena sifat serverless yang bisa menjalankan banyak instance sekaligus. Untuk project skala belajar, ini belum jadi masalah berarti. Untuk project produksi sungguhan dengan trafik tinggi, praktik lanjutannya adalah menyimpan (cache) koneksi Mongoose yang sudah ada supaya tidak membuat koneksi baru di setiap pemanggilan — topik ini bisa didalami lebih lanjut lewat dokumentasi resmi Mongoose tentang serverless.


4. Membuat File Konfigurasi vercel.json

Analogi: vercel.json berperan seperti resepsionis pusat yang mengarahkan semua tamu yang datang dari pintu mana pun, ke satu ruangan yang sama — ruangan itu adalah api/index.js yang baru kita buat. Tanpa file ini, Vercel tidak tahu bahwa setiap URL yang diakses (baik /, /api/todos, maupun /api-docs) semuanya harus diteruskan ke aplikasi Express yang sama.

Buat file berikut di root project:

vercel.json

{
  "version": 2,
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/api/index.js"
    }
  ]
}

Penjelasan teknis: rewrites dengan pola "source": "/(.*)" artinya “URL apa pun, tanpa terkecuali” (tanda kurung dengan titik-bintang adalah regex yang menangkap seluruh path). Semua request itu diarahkan ke destination yang sama, yaitu handler api/index.js. Ini yang memungkinkan satu aplikasi Express dengan puluhan route berbeda (dari route auth, todo, sampai stats yang kita bangun sepanjang seri ini) tetap bisa dilayani lewat satu serverless function saja, alih-alih harus membuat function terpisah untuk tiap endpoint.


5. Push Perubahan ke GitHub

Simpan dan unggah kedua file baru ini mengikuti alur kerja yang sudah kita pelajari di artikel sebelumnya:

git add .
git commit -m "Add Vercel serverless entry point and configuration"
git push

6. Menghubungkan Repository ke Dashboard Vercel

  1. Login ke vercel.com (pakai akun GitHub yang sama).

  2. Klik Add New...Project.

  3. Pilih repository Todo List API dari daftar yang muncul, klik Import.

  4. Pada bagian Root Directory, biarkan default (./) — jangan diubah ke folder api/, karena Vercel tetap butuh mengakses package.json dan folder src/ yang berada di root project, bukan hanya isi folder api/ saja.

  5. Buka bagian Environment Variables, lalu tambahkan seluruh variabel berikut satu per satu (nilainya sama dengan isi .env lokal kita, kecuali MONGODB_URI yang harus memakai connection string MongoDB Atlas):

Key

Value

Keterangan

MONGODB_URI

Connection string MongoDB Atlas

Wajib bisa diakses dari internet, bukan localhost

JWT_SECRET

String rahasia (beda dari development, sesuai praktik baik di artikel README)

Untuk menandatangani token JWT

JWT_EXPIRES_IN

Misalnya 1d

Masa berlaku token

EXTERNAL_API_KEY

String rahasia (beda dari development)

Proteksi endpoint /api/stats

NODE_ENV

production

Menyembunyikan stack trace error, sesuai penjelasan di artikel README

  1. Klik Deploy, lalu tunggu proses build selesai.

Penjelasan teknis: mengisi environment variable lewat dashboard ini persis seperti yang sudah diprediksi di artikel README — platform hosting modern umumnya tidak menerima file .env diunggah langsung, melainkan lewat form seperti ini. Prinsip yang sama tetap berlaku: kredensial sensitif tidak pernah ditulis di kode maupun ikut ter-commit ke Git, hanya diinput manual di tempat yang aman seperti dashboard hosting.

6.1 Bukti Konfigurasi Dashboard

halaman import proyek ke vercel

Halaman Import Project — pemilihan repository GitHub dan pengaturan Root Directory yang dibiarkan default.

tampilan konfigurasi env variable di vercellanjutan tampilan env variable di vercel

Halaman Environment Variables — seluruh variabel (MONGODB_URI, JWT_SECRET, JWT_EXPIRES_IN, EXTERNAL_API_KEY, NODE_ENV) sudah terisi sebelum proses deploy dimulai.


7. Verifikasi Deployment

Setelah proses build selesai, Vercel akan memberikan URL publik (contoh: https://todo-api-namamu.vercel.app). Lakukan pengecekan berikut:

  • Buka URL utama — harus muncul response JSON { "message": "Todo API is running" } seperti yang kita definisikan di app.js sejak artikel pertama.

  • Buka [URL]/api-docs — harus menampilkan halaman Swagger UI dari artikel kedelapan.

  • Coba endpoint POST [URL]/api/auth/register lewat Postman, ganti {{baseUrl}} di environment Postman-mu ke URL Vercel yang baru.

tampilan berhasil deploy proyek di vercel

Halaman deployment sukses di dashboard Vercel, lengkap dengan URL publik yang dihasilkan, misalnya: https://backend-todos-list.vercel.app.

halaman utama berhasil di deploytampilan halaman swagger di halaman utama deployment

Response JSON dari URL utama, dan halaman Swagger UI (/api-docs) yang sudah bisa diakses publik.


8. Troubleshooting: Masalah Umum dan Solusinya

Berikut tiga masalah paling sering ditemui saat deploy pertama kali ke Vercel, beserta cara mendiagnosis dan memperbaikinya.

8.1 Error 500: "Cannot find module '../utils/AppError'"

Analogi: ini seperti surat undangan yang menuliskan nama “Budi” padahal nama asli di kartu keluarga tertulis “budi” — di lingkungan yang “santai” (macOS/Windows), perbedaan huruf besar-kecil ini sering diabaikan begitu saja. Tapi di lingkungan yang “ketat” (Linux, termasuk server Vercel), nama harus cocok persis huruf demi huruf, atau dianggap sebagai dua hal yang sama sekali berbeda.

Penyebab: macOS dan Windows secara default bersifat case-insensitive untuk nama file (AppError.js dan apperror.js dianggap file yang sama), sedangkan server Vercel berjalan di atas Linux yang bersifat case-sensitive (dua nama itu dianggap dua file berbeda). Kalau nama file fisik di folder utils/ tidak persis sama hurufnya dengan yang dipanggil lewat require(...) di kode — misalnya file bernama AppError.js tapi suatu tempat di kode memanggilnya lewat require("../utils/apperror") — kodenya akan tetap berjalan normal di laptop macOS/Windows, tapi gagal total begitu di-deploy ke Vercel.

Cara memeriksa log error: buka menu Logs (atau Runtime Logs) pada halaman project di dashboard Vercel untuk melihat pesan error lengkap seperti ini.

Tampilan log error case Sensitive

Halaman Runtime Logs di dashboard Vercel, menampilkan detail error "Cannot find module" beserta path file yang bermasalah.

Solusi: pastikan seluruh statement require(...) di setiap file controller, service, dan middleware menuliskan nama file persis sama hurufnya dengan nama file fisik di disk. Konsisten dengan penamaan yang kita pakai sejak artikel error handling, file kita bernama utils/AppError.js (huruf A besar), sehingga semua pemanggilannya di seluruh project harus konsisten:

// Benar — cocok persis dengan nama file fisik utils/AppError.js
const AppError = require("../utils/AppError");

// Salah — akan lolos di macOS/Windows, tapi GAGAL di Vercel (Linux)
const AppError = require("../utils/appError");

Lakukan pengecekan ini di seluruh file yang mengimpor AppError, catchAsync, atau modul lokal lain mana pun sebelum melakukan deploy, supaya masalah case-sensitivity ini tidak lolos ke production.

8.2 Tampilan Swagger UI Rusak (CSS & JS Gagal Dimuat)

Penyebab: Vercel Serverless Function secara default tidak melayani file statis dari dalam node_modules (dalam hal ini node_modules/swagger-ui-dist, tempat CSS dan JS bawaan Swagger UI berada) seperti halnya server tradisional. Akibatnya, halaman /api-docs tetap muncul tapi tanpa styling maupun interaktivitas sama sekali.

Solusi: arahkan Swagger UI untuk memuat asset CSS dan JS-nya dari CDN publik, bukan dari file lokal di server.

src/app.js (bagian yang diubah dari konfigurasi Swagger di artikel kedelapan)

const CSS_URL = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.0.0/swagger-ui.min.css";
const JS_URL = [
  "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.0.0/swagger-ui-bundle.js",
  "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.0.0/swagger-ui-standalone-preset.js",
];

app.use(
  "/api-docs",
  swaggerUi.serve,
  swaggerUi.setup(swaggerSpec, { customCssUrl: CSS_URL, customJs: JS_URL })
);

Penjelasan teknis: opsi kedua pada swaggerUi.setup(swaggerSpec, options) yang sebelumnya kita panggil tanpa argumen tambahan di artikel kedelapan, sekarang diisi customCssUrl dan customJs. Browser akan memuat file CSS/JS Swagger UI langsung dari CDN Cloudflare, bukan meminta server Vercel menyajikannya — sehingga halaman /api-docs kembali tampil dan berfungsi normal, sekaligus sedikit meringankan beban serverless function karena tidak perlu ikut membundel asset statis tersebut.

8.3 Swagger UI Error "Failed to Fetch" Saat Klik "Try It Out"

Penyebab: di src/config/swagger.js pada artikel kedelapan, kita menuliskan servers: [{ url: "http://localhost:3000", ... }] secara hardcode. Begitu halaman ini dibuka dari domain Vercel (https://...) dan mencoba mengirim request lewat tombol Try it out, browser tetap mencoba mengirim request ke http://localhost:3000 — alamat yang sama sekali tidak ada hubungannya dengan domain Vercel tempat halaman itu dibuka — sehingga diblokir oleh browser karena danya perbedaan origin/protocol (masalah CORS).

Solusi bagian 1 — ubah servers jadi URL relatif:

src/config/swagger.js (bagian yang diubah)

servers: [
  { url: "/", description: "Current Server (Auto Detect / Vercel)" },
  { url: "http://localhost:3000", description: "Local development server" },
],

Penjelasan teknis: dengan url: "/", Swagger UI akan otomatis mengirim request ke domain yang sama dengan tempat halaman /api-docs itu dibuka — kalau dibuka dari https://todo-api-namamu.vercel.app/api-docs, tombol Try it out otomatis mengirim ke https://todo-api-namamu.vercel.app/api/..., bukan lagi ke localhost. Opsi http://localhost:3000 tetap dibiarkan sebagai pilihan kedua di dropdown, supaya dokumentasi yang sama tetap bisa dipakai saat development lokal.

Solusi bagian 2 — install dan aktifkan middleware CORS:

npm install cors

src/app.js (tambahkan di bagian atas, sebelum route didaftarkan)

const cors = require("cors");

app.use(cors());

Sehingga kode lengkap app.js menjadi:

const express = require("express");
const cors = require("cors");
const swaggerUi = require("swagger-ui-express");
const swaggerSpec = require("./config/swagger");
const todoRoutes = require("./routes/todo.routes");
const authRoutes = require("./routes/auth.routes");
const statsRoutes = require("./routes/stats.routes");
const logger = require("./middlewares/logger.middleware");
const notFound = require("./middlewares/notFound.middleware");
const errorHandler = require("./middlewares/errorHandler.middleware");

const app = express();

app.use(cors());
app.use(logger);
app.use(express.json());

app.get("/", (req, res) => {
  res.json({ message: "Todo API is running" });
});

// Halaman dokumentasi interaktif tersedia di /api-docs
const CSS_URL = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.0.0/swagger-ui.min.css";
const JS_URL = [
  "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.0.0/swagger-ui-bundle.js",
  "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.0.0/swagger-ui-standalone-preset.js",
];

app.use(
  "/api-docs",
  swaggerUi.serve,
  swaggerUi.setup(swaggerSpec, {
    customCssUrl: CSS_URL,
    customJs: JS_URL,
  })
);

app.use("/api/auth", authRoutes);
app.use("/api/todos", todoRoutes);
app.use("/api/stats", statsRoutes);

app.use(notFound);
app.use(errorHandler);

module.exports = app;

Penjelasan teknis: CORS (Cross-Origin Resource Sharing) adalah mekanisme keamanan bawaan browser yang membatasi halaman web mengirim request ke domain lain kecuali diizinkan secara eksplisit oleh server tujuan. Middleware cors() tanpa argumen ini mengizinkan request dari origin mana pun — pilihan paling sederhana dan cukup aman untuk REST API publik seperti Todo List API kita. Middleware ini sebaiknya dipasang di awal, sejajar dengan express.json() yang sudah kita pasang sejak artikel kedua, supaya berlaku untuk seluruh route yang didaftarkan setelahnya.

Screenshot swagger response berhasil

Tombol "Try it out" di Swagger UI berhasil mengirim request dan menerima response setelah URL server dan middleware CORS diperbaiki.

8.4 Error "MongooseServerSelectionError" / Koneksi Database Timeout

Penyebab: IP address Vercel belum diizinkan di menu Network Access MongoDB Atlas — database menolak percobaan koneksi karena menganggap IP yang mencoba konek tidak dikenal/tidak terdaftar. Ini adalah kelanjutan langsung dari langkah persiapan di bagian 2.1; kalau langkah itu terlewat, gejalanya baru terlihat sekarang, saat aplikasi sudah coba jalan di Vercel dan gagal terhubung ke database.

Cara memastikan ini penyebabnya: buka menu Logs di dashboard Vercel, cari pesan error yang menyebut MongooseServerSelectionError, connection timed out, atau IP that isn't whitelisted.

Solusi: kembali ke dashboard MongoDB Atlas → Network Access → pastikan entri 0.0.0.0/0 sudah ditambahkan dan berstatus Active (bukan Pending) seperti dijelaskan di bagian 2.1. Setelah entri aktif, tidak perlu deploy ulang — cukup coba akses endpoint lagi setelah beberapa saat, karena perubahan Network Access di Atlas biasanya butuh waktu singkat untuk diterapkan.


9. Checklist Verifikasi Akhir

  • ☐ File api/index.js dan vercel.json sudah dibuat dan ter-push ke GitHub

  • ☐ MongoDB Atlas sudah mengizinkan akses dari 0.0.0.0/0 di menu Network Access

  • MONGODB_URI memakai MongoDB Atlas, bukan localhost

  • ☐ Seluruh environment variable sudah diisi di dashboard Vercel, termasuk NODE_ENV=production

  • ☐ URL utama menampilkan response JSON tanda server hidup

  • ☐ Halaman /api-docs tampil dengan styling normal dan tombol Try it out berfungsi

  • ☐ Endpoint auth, todo, dan stats sudah dites lewat Postman memakai URL Vercel yang baru

  • ☐ Tidak ada error di menu Runtime Logs saat endpoint diakses


10. Kesimpulan

Dengan menambahkan api/index.js sebagai entry point serverless dan vercel.json sebagai pengatur routing, Todo List API yang kita bangun sepanjang sebelas artikel sebelumnya kini benar-benar bisa diakses secara publik lewat internet — bukan lagi cuma berjalan di localhost. Struktur folder yang rapi sejak artikel pertama, terutama pemisahan app.js dan server.js, terbukti membuat proses adaptasi ke platform serverless jadi jauh lebih mudah dibanding kalau seluruh logic tercampur di satu file.

Tiga masalah yang dibahas di bagian troubleshooting — case sensitivity, asset statis Swagger, dan CORS — adalah masalah yang hampir pasti ditemui siapa pun yang pertama kali men-deploy backend Express ke platform serverless, bukan cuma khas project Todo List ini. Memahami penyebabnya, bukan cuma menghafal solusinya, akan sangat membantu ketika kamu nanti men-deploy project lain di luar seri ini.

Artikel Terkait

Cara Restrukturisasi CRUD Node.js: Dari Function ke Folder Controller-Service-Model

cara merestrukturisasi kode CRUD dari kumpulan function menjadi struktur folder yang rapi menggunakan pola Controller-Service-Model.

Baca Artikel
Panduan Lengkap Membuat Aplikasi Web dengan React JS

Pelajari cara membuat aplikasi web menggunakan React JS dalam tutorial langkah demi langkah ini.

Baca Artikel
Cara Membuat Efek Parallax Mudah dengan parallaxx.js

Pelajari cara membuat efek parallax yang menakjubkan menggunakan parallax.js dalam tutorial ini.

Baca Artikel