
Pastikan Anda telah menginstal:
Buka terminal/command prompt dan jalankan:
node --version
npm --version
Penjelasan Command:
--version atau -v adalah flag untuk menampilkan versi yang ter-installScreenshot 1: Terminal menampilkan versi Node.js dan npm
$ node --version
v18.17.0
$ npm --version
9.6.7
mkdir pertemuan-2
cd pertemuan-2
Penjelasan Command:
mkdir pertemuan-2: Membuat folder baru dengan nama “pertemuan-2” (make directory)cd pertemuan-2: Change directory - masuk ke dalam folder tersebutnpm init -y
Penjelasan Command:
npm init: Command untuk inisialisasi project Node.js, membuat file package.json-y atau --yes: Skip semua pertanyaan dan gunakan default value-y, npm akan menanyakan:
package.json berisi metadata project dan daftar dependencies
npm init -y
Perintah ini akan membuat file `package.json` secara otomatis dengan konfigurasi default.
Wrote to /path/to/pertemuan-2/package.json: { “name”: “pertemuan-2”, “version”: “1.0.0”, … }
---
## 3. Instalasi Dependencies
### Langkah 3.1: Install Dependencies Utama
```bash
npm install express express-handlebars better-sqlite3
Penjelasan Dependencies:
express-handlebars: Template engine yang memungkinkan kita membuat file HTML dinamis. Dengan Handlebars, kita bisa memasukkan data dari server ke dalam HTML menggunakan syntax seperti ``. Ini memisahkan logika aplikasi dari tampilan (View).
npm install --save-dev nodemon
Penjelasan:
Catatan: Flag --save-dev berarti nodemon hanya diperlukan saat development, tidak akan diikutkan saat aplikasi di-deploy ke production.
Tambahkan script untuk development. File package.json adalah file konfigurasi project Node.js yang berisi informasi metadata project dan dependencies. Bagian scripts memungkinkan kita membuat shortcut command:
{
"name": "pertemuan-2",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon index.js"
},
"dependencies": {
"better-sqlite3": "^12.6.2",
"express": "^5.2.1",
"express-handlebars": "^8.0.2"
},
"devDependencies": {
"nodemon": "^3.1.14"
}
}
Penjelasan isi package.json:
"name": Nama project (otomatis dari nama folder)"version": Versi aplikasi (mengikuti Semantic Versioning)"main": File entry point aplikasi"scripts": Perintah-perintah yang bisa dijalankan dengan npm run
"dev": Menjalankan aplikasi dengan nodemon untuk developmentnpm run dev"dependencies": Library yang dibutuhkan aplikasi untuk berjalan (perlu di production)"devDependencies": Library yang hanya dibutuhkan saat developmentmkdir views
mkdir views/layouts
mkdir views/mahasiswa
Penjelasan struktur folder:
views/: Folder utama untuk menyimpan semua file template Handlebarsviews/layouts/: Folder untuk menyimpan layout template (struktur HTML dasar yang dipakai bersama)views/mahasiswa/: Folder khusus untuk halaman-halaman terkait mahasiswa (add dan edit)Pemisahan folder ini mengikuti prinsip separation of concerns - memisahkan file berdasarkan fungsinya agar lebih terorganisir.
pertemuan-2/
├── index.js
├── package.json
├── views/
│ ├── index.hbs
│ ├── layouts/
│ │ └── main.hbs
│ └── mahasiswa/
│ ├── add.hbs
│ └── edit.hbs
Buat file index.js di root folder dan tambahkan kode berikut:
// import express module
const express = require("express");
// import path module for handling file paths
const path = require("path");
const bodyParser = require("body-parser");
const { engine } = require("express-handlebars");
const DB = require("better-sqlite3");
const db = new DB("mahasiswa.db");
// create mahasiswa table if it doesn't exist
db.exec(`
CREATE TABLE IF NOT EXISTS mahasiswa (
nim TEXT PRIMARY KEY,
nama TEXT NOT NULL,
alamat TEXT NOT NULL
)
`);
// set up handlebars as the view engine
const hbs = engine({
extname: "hbs", // set file extension for handlebars templates
defaultLayout: "main", // set default layout file
layoutsDir: path.join(__dirname, "views", "layouts"), // set directory for layout files
helpers: {
noUrut: (index) => index + 1, // helper function to generate row numbers in the table
},
});
// create express app
const app = express();
// parse URL-encoded bodies (for form submissions)
app.use(bodyParser.urlencoded({ extended: false }));
// set handlebars as the view engine for the app
app.engine("hbs", hbs);
app.set("view engine", "hbs");
app.set("views", path.join(__dirname, "views")); // set directory for view templates
// define a route for GET request to '/' => root page
app.get("/", (req, res) => {
// fetch all mahasiswa records from the database
const mahasiswa = db.prepare("SELECT * FROM mahasiswa").all();
res.render("index", {
mahasiswa, // pass the mahasiswa data to the index.hbs template
}); // render index.hbs template
});
app.get("/mahasiswa/add", (req, res) => {
res.render("mahasiswa/add");
});
app.get("/mahasiswa/edit/:nim", (req, res) => {
const { nim } = req.params; // extract nim from the URL parameters
const mahasiswa = db
.prepare("SELECT * FROM mahasiswa WHERE nim = ?")
.get(nim); // fetch the mahasiswa record with the specified nim from the database
res.render("mahasiswa/edit", {
mahasiswa, // pass the mahasiswa data to the edit.hbs template
});
});
app.post("/mahasiswa/add", (req, res) => {
// extract nim, nama, and alamat from the request body
const { nim, nama, alamat } = req.body;
db.prepare(
`INSERT INTO mahasiswa (nim, nama, alamat)
VALUES (?, ?, ?)`,
).run(nim, nama, alamat);
res.redirect("/"); // redirect to the root page after adding a new mahasiswa
});
app.post("/mahasiswa/edit/:nim", (req, res) => {
const { nim } = req.params; // extract nim from the URL parameters
const { nama, alamat } = req.body; // extract nama and alamat from the request body
db.prepare("UPDATE mahasiswa SET nama = ?, alamat = ? WHERE nim = ?").run(
nama,
alamat,
nim,
);
res.redirect("/"); // redirect to the root page after updating the mahasiswa record
});
app.post("/mahasiswa/delete/:nim", (req, res) => {
const { nim } = req.params; // extract nim from the URL parameters
db.prepare("DELETE FROM mahasiswa WHERE nim = ?").run(nim); // delete the mahasiswa record with the specified nim from the database
res.redirect("/"); // redirect to the root page after deleting the mahasiswa record
});
// start the server on port 3000
app.listen(3000, () => {
console.log("Server is running on http://localhost:3000");
});
const express = require("express");
const path = require("path");
const bodyParser = require("body-parser");
const { engine } = require("express-handlebars");
const DB = require("better-sqlite3");
Penjelasan masing-masing module:
express: Module utama untuk membuat web server dan routingpath: Module bawaan Node.js untuk handle path file/folder secara cross-platform (Windows menggunakan \, Linux/Mac menggunakan /)bodyParser: Middleware untuk mem-parsing data dari form HTML yang dikirim melalui POST request. Data form dikodekan sebagai URL-encoded string, bodyParser mengubahnya menjadi JavaScript object{ engine }: Mengimport function engine dari express-handlebars menggunakan destructuring syntax ES6DB: Constructor untuk membuat koneksi database SQLiteconst db = new DB("mahasiswa.db");
db.exec(`
CREATE TABLE IF NOT EXISTS mahasiswa (
nim TEXT PRIMARY KEY,
nama TEXT NOT NULL,
alamat TEXT NOT NULL
)
`);
Penjelasan detail:
new DB("mahasiswa.db"): Membuat/membuka koneksi ke database SQLite. Jika file mahasiswa.db belum ada, akan otomatis dibuat. File ini akan tersimpan di root folder project.
db.exec(): Method untuk menjalankan SQL statement. Digunakan untuk operasi yang tidak return data (CREATE, DROP, dll).
CREATE TABLE IF NOT EXISTS: Perintah SQL untuk membuat tabel. IF NOT EXISTS memastikan tabel hanya dibuat jika belum ada, sehingga tidak error saat aplikasi di-restart.
Struktur tabel:
nim TEXT PRIMARY KEY: Kolom NIM dengan tipe data TEXT dan dijadikan PRIMARY KEY (unique identifier, tidak boleh duplikat, tidak boleh NULL)nama TEXT NOT NULL: Kolom nama dengan tipe TEXT, wajib diisi (NOT NULL)alamat TEXT NOT NULL: Kolom alamat dengan tipe TEXT, wajib diisiCatatan: SQLite hanya memiliki beberapa tipe data: NULL, INTEGER, REAL, TEXT, BLOB. Dalam praktiknya TEXT bisa menyimpan string dengan panjang apapun.
const hbs = engine({
extname: "hbs",
defaultLayout: "main",
layoutsDir: path.join(__dirname, "views", "layouts"),
helpers: {
noUrut: (index) => index + 1,
},
});
Penjelasan konfigurasi:
extname: "hbs": Menentukan ekstensi file template yang digunakan. Default-nya .handlebars, kita ubah menjadi .hbs agar lebih singkat.
defaultLayout: "main": Menentukan layout default yang akan digunakan semua halaman. File layout adalah “kerangka” HTML yang berisi struktur dasar (header, footer, dll). Setiap halaman akan di-render di dalam layout ini.
layoutsDir: path.join(__dirname, "views", "layouts"): Menentukan lokasi folder layouts. __dirname adalah variable global Node.js yang berisi path absolut folder tempat file JavaScript berjalan. path.join() menggabungkan path secara cross-platform.
helpers: Object berisi helper functions yang bisa dipanggil di dalam template Handlebars.
noUrut: (index) => index + 1: Helper function untuk penomoran. Karena array dimulai dari index 0, kita tambah 1 agar penomoran dimulai dari 1.Apa itu Helper? Helper adalah custom function yang bisa kita buat untuk digunakan dalam template Handlebars. Berguna untuk logic sederhana seperti formatting tanggal, penomoran, conditional logic, dll.
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.engine("hbs", hbs);
app.set("view engine", "hbs");
app.set("views", path.join(__dirname, "views"));
Penjelasan detail konfigurasi Express:
const app = express(): Membuat instance Express application. Object app ini adalah aplikasi web kita, yang nanti akan digunakan untuk definisi routes, middleware, settings, dll.
app.use(bodyParser.urlencoded({ extended: false })):
app.use() memasang middleware yang akan dijalankan untuk setiap requestbodyParser.urlencoded() mem-parsing data form HTML yang dikirim dengan encoding application/x-www-form-urlencoded (format default form HTML){ extended: false } menggunakan library querystring untuk parsing (alternatifnya qs library jika true)req.body akan undefined saat kita submit formapp.engine("hbs", hbs): Mendaftarkan Handlebars sebagai template engine dengan extension “.hbs”. Memberitahu Express bahwa file dengan extension .hbs harus di-render menggunakan engine yang sudah kita konfigurasi.
app.set("view engine", "hbs"): Mengatur view engine default menjadi “hbs”. Dengan ini, kita tidak perlu menulis extension saat render view (cukup res.render("index") tanpa "index.hbs").
app.set("views", path.join(__dirname, "views")): Mengatur lokasi folder views. Express akan mencari file template di folder ini.Apa itu Middleware? Middleware adalah function yang memproses request sebelum sampai ke route handler. Middleware bisa mengubah request/response object, mengakhiri request-response cycle, atau memanggil middleware berikutnya dalam stack.
Route GET ‘/’ - Halaman Utama
app.get("/", (req, res) => {
const mahasiswa = db.prepare("SELECT * FROM mahasiswa").all();
res.render("index", { mahasiswa });
});
Penjelasan step by step:
app.get("/", ...): Mendefinisikan route untuk HTTP GET request ke path “/” (root/homepage)(req, res) => { ... }: Callback function yang akan dipanggil saat route ini diakses
req (request): Object yang berisi informasi tentang HTTP request (headers, parameters, body, dll)res (response): Object untuk mengirim response ke clientdb.prepare("SELECT * FROM mahasiswa"): Membuat prepared statement SQL. Prepared statement lebih aman dari SQL injection dan lebih efisien untuk query yang dijalankan berulang kali.
"SELECT * FROM mahasiswa": Query SQL untuk mengambil semua kolom dari semua baris di tabel mahasiswa.all(): Method untuk mendapatkan semua hasil query dalam bentuk array of objects. Contoh hasilnya:
[
{ nim: "101", nama: "Ahmad", alamat: "Jakarta" },
{ nim: "102", nama: "Siti", alamat: "Bandung" },
];
res.render("index", { mahasiswa }): Render template “index.hbs” dan passing data
{ mahasiswa } adalah shorthand ES6 untuk { mahasiswa: mahasiswa }Route GET ‘/mahasiswa/add’ - Halaman Tambah Data
app.get("/mahasiswa/add", (req, res) => {
res.render("mahasiswa/add");
});
Penjelasan:
res.render("mahasiswa/add"): Render template yang ada di views/mahasiswa/add.hbsRoute GET ‘/mahasiswa/edit/:nim’ - Halaman Edit Data
app.get("/mahasiswa/edit/:nim", (req, res) => {
const { nim } = req.params;
const mahasiswa = db
.prepare("SELECT * FROM mahasiswa WHERE nim = ?")
.get(nim);
res.render("mahasiswa/edit", { mahasiswa });
});
Penjelasan detail:
"/mahasiswa/edit/:nim": Path dengan route parameter. “:nim” adalah placeholder dinamis
/mahasiswa/edit/101 → :nim akan bernilai “101”/mahasiswa/edit/102 → :nim akan bernilai “102”const { nim } = req.params: Destructuring untuk mengambil nilai parameter dari URL
req.params adalah object yang berisi semua route parameters/mahasiswa/edit/101, maka req.params = { nim: "101" }db.prepare("SELECT * FROM mahasiswa WHERE nim = ?"): Prepared statement dengan placeholder ?
? akan digantikan dengan nilai parameter yang kita berikan.get(nim): Method untuk mendapatkan satu baris hasil query
.all() yang return array, .get() return satu object atau undefinednim akan menggantikan tanda ? dalam query{ nim: "101", nama: "Ahmad", alamat: "Jakarta" }res.render("mahasiswa/edit", { mahasiswa }): Render form edit dengan data mahasiswa yang sudah ada, sehingga form terisi otomatis dengan data currentRoute POST ‘/mahasiswa/add’ - Proses Tambah Data
app.post("/mahasiswa/add", (req, res) => {
const { nim, nama, alamat } = req.body;
db.prepare(`INSERT INTO mahasiswa (nim, nama, alamat) VALUES (?, ?, ?)`).run(
nim,
nama,
alamat,
);
res.redirect("/");
});
Route POST ‘/mahasiswa/edit/:nim’ - Proses Edit Data
app.post("/mahasiswa/edit/:nim", (req, res) => {
const { nim } = req.params;
const { nama, alamat } = req.body;
db.prepare("UPDATE mahasiswa SET nama = ?, alamat = ? WHERE nim = ?").run(
nama,
alamat,
nim,
);
res.redirect("/");
});
Penjelasan detail:
const { nim } = req.params: Mengambil NIM dari URL parameter
/mahasiswa/edit/101, maka nim = “101”const { nama, alamat } = req.body: Mengambil data yang diubah dari form
db.prepare("UPDATE mahasiswa SET nama = ?, alamat = ? WHERE nim = ?"):
UPDATE mahasiswa: Statement SQL untuk update dataSET nama = ?, alamat = ?: Kolom yang akan diupdate dengan nilai baruWHERE nim = ?: Kondisi untuk menentukan baris mana yang akan diupdate.run(nama, alamat, nim): Execute update dengan parameter berurutan
res.redirect("/"): Kembali ke halaman utama setelah update berhasilRoute POST ‘/mahasiswa/delete/:nim’ - Proses Hapus Data
app.post("/mahasiswa/delete/:nim", (req, res) => {
const { nim } = req.params;
db.prepare("DELETE FROM mahasiswa WHERE nim = ?").run(nim);
res.redirect("/");
});
Penjelasan detail:
const { nim } = req.params: Mengambil NIM dari URL parameter
db.prepare("DELETE FROM mahasiswa WHERE nim = ?"): Prepared statement untuk DELETE
DELETE FROM mahasiswa: Perintah SQL untuk hapus data dari tabel mahasiswaWHERE nim = ?: Kondisi untuk menentukan baris mana yang akan dihapus.run(nim): Execute delete operation
res.redirect("/"): Redirect ke halaman utama
Catatan Keamanan: Dalam aplikasi production, sebaiknya tambahkan konfirmasi di backend dan authorization untuk memastikan user berhak menghapus data tersebut.
app.listen(3000, () => {
console.log("Server is running on http://localhost:3000");
});
Penjelasan:
app.listen(port, callback): Method untuk menjalankan server
app.listen() dipanggil, server akan “mendengarkan” request HTTP yang masukCara akses: Buka browser dan ketik http://localhost:3000
localhost = komputer sendiri (127.0.0.1)3000 = port numberBuat file views/layouts/main.hbs:
Apa itu Layout? Layout adalah template “kerangka” yang berisi struktur HTML dasar (head, body, dll) yang sama untuk semua halaman. Dengan layout, kita tidak perlu menulis ulang struktur HTML yang sama di setiap halaman. Setiap halaman hanya perlu menulis konten uniknya saja.
Penjelasan elemen-elemen:
<html lang="en">: Tag root HTML dengan bahasa English
<meta charset="UTF-8" />: Set character encoding UTF-8 (support semua karakter internasional)
<meta name="viewport" ...>: Penting untuk responsive design. Memberitahu browser untuk adjust ukuran content sesuai device width (mobile-friendly).
<title>: Judul yang muncul di tab browser
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/...">: Load Bootstrap CSS dari CDN (Content Delivery Network)
integrity dan crossorigin untuk keamanan (mencegah file di-modifikasi)<div class="container">: Class Bootstrap yang memberikan:
}: Triple curly braces untuk unescaped HTML
} digunakan agar HTML tidak di-escape (berbeda dengan double braces `` yang escape HTML)}Bagaimana cara kerjanya?
Ketika kita melakukan res.render("index", data), Express-Handlebars akan:
}Buat file views/index.hbs:
Struktur Halaman:
<h1>List Data Mahasiswa</h1>: Heading utama halaman<hr />: Horizontal rule (garis pemisah)Button Tambah Data:
<a href="/mahasiswa/add" class="btn btn-primary mb-3">: Link yang di-styling seperti button
href="/mahasiswa/add": Link ke halaman tambah dataclass="btn btn-primary": Class Bootstrap untuk button birumb-3: Margin bottom (spacing ke elemen di bawahnya)Tabel Data:
<table class="table table-bordered">: Tabel dengan border
table: Class Bootstrap untuk styling tabeltable-bordered: Menambahkan border pada semua cell<thead>: Table head (header tabel)<th>: Table header cell untuk judul kolomHandlebars Syntax:
...: Block helper untuk looping
noUrut dengan parameter @index
@index adalah variable special Handlebars yang berisi index array (0, 1, 2, …)noUrut menambah 1 sehingga jadi 1, 2, 3, …,, ``: Mengakses property dari object current iteration
this merujuk ke item mahasiswa pada iterasi saat inithis: ,, ``Button Edit:
<a href="/mahasiswa/edit/" class="btn btn-warning">: Link ke halaman edit
/mahasiswa/edit/101btn-warning: Button kuning (Bootstrap color)Form Delete:
action="/mahasiswa/delete/": URL target dengan NIM dinamismethod="POST": HTTP method POST (tidak bisa GET karena GET tidak boleh ubah data)style="display: inline-block;": Agar form dan button Edit sejajar (inline)
<button type="submit" ... onclick="return confirm('...')">: Button submit dengan konfirmasi
onclick="return confirm('...')": JavaScript untuk menampilkan dialog confirm browserbtn-danger: Button merah (Bootstrap color untuk action berbahaya)Catatan Penting:
......Buat file views/mahasiswa/add.hbs:
Form HTML:
<form action="/mahasiswa/add" method="POST">: Tag form untuk input data
action="/mahasiswa/add": URL tujuan saat form di-submit. Harus match dengan route di index.jsmethod="POST": HTTP method yang digunakan. POST untuk mengirim data sensitif/banyak/mahasiswa/add dengan data formStructure Form Bootstrap:
<div class="mb-3">: Wrapper untuk setiap form group
mb-3: Margin bottom class Bootstrap (spacing antar input)<label for="nim" class="form-label">: Label untuk input
for="nim": Menghubungkan label dengan input (saat label di-klik, input akan focus)class="form-label": Bootstrap styling untuk label<input type="text" class="form-control" id="nim" name="nim" required />: Input field
type="text": Tipe input text biasa (bisa diganti number, email, dll)class="form-control": Bootstrap class untuk styling input (border, padding, dll)id="nim": ID unik untuk input, harus match dengan for di labelname="nim": PENTING! Ini adalah key yang akan digunakan di req.body.nim di backendrequired: HTML5 validation - form tidak bisa di-submit jika kosongTiga Input Fields:
Button Submit:
<button type="submit" class="btn btn-primary">: Button untuk submit form
type="submit": Saat di-klik, akan trigger form submission<input type="submit" value="Simpan" />btn-primary: Bootstrap blue buttonFlow Submit Form:
/mahasiswa/addnim=101&nama=Ahmad&alamat=Jakarta{ nim: "101", nama: "Ahmad", alamat: "Jakarta" }req.body di route handlerKenapa tidak pakai GET? GET tidak cocok untuk form karena:
Buat file views/mahasiswa/edit.hbs:
Perbedaan dengan add.hbs:
action="/mahasiswa/edit/": URL action yang mengandung NIM/mahasiswa/edit/101value="": Mengisi input dengan data yang sudah adavalue="": Form terisi otomatis (pre-filled)value="": User bisa langsung edit tanpa ketik ulang<input ... value="" readonly />: NIM tidak bisa diubahreadonly membuat input tidak bisa di-edit (greyed out)Flow Edit Data:
/mahasiswa/edit/101/mahasiswa/edit/101 dengan data baruKenapa NIM di URL dan di Form?
Alternatif: Hidden Input Bisa juga menggunakan hidden input tanpa menampilkan NIM:
<input type="hidden" name="nim" value="" />
Tapi lebih baik tampilkan NIM (readonly) agar user tahu data mana yang sedang diedit.
Buka terminal di folder project dan jalankan:
npm run dev
Penjelasan:
npm run dev: Menjalankan script “dev” yang sudah kita definisikan di package.jsonnodemon index.jsAlternative tanpa nodemon:
node index.js
[nodemon] 3.1.14
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node index.js`
Server is running on http://localhost:3000
Penjelasan Output:
[nodemon] 3.1.14: Versi nodemon yang digunakanto restart at any time, enter 'rs': Ketik rs + Enter untuk manual restartwatching path(s): *.*: Nodemon memonitor semua file di folderwatching extensions: ...: File extension yang dimonitorstarting 'node index.js': Command yang dijalankan nodemonServer is running...: Output dari console.log di index.js kitaTroubleshooting:
npm install untuk install dependenciessudo npm run dev atau fix permissionBuka browser dan akses:
http://localhost:3000
Penjelasan URL:
http://: Protocol (Hypertext Transfer Protocol)localhost: Hostname yang merujuk ke komputer sendiri (127.0.0.1)3000: Port number tempat server berjalanhttp://127.0.0.1:3000
Yang Akan Muncul:
Catatan:

Apa yang Terjadi di Balik Layar:
/mahasiswa/addapp.get("/mahasiswa/add", ...)mahasiswa/add.hbsmain.hbs101Ahmad FaisalJl. Merdeka No. 10, Jakarta
Tips Pengisian:

List Data Mahasiswa
[Tambah Data]
No NIM Nama Alamat Aksi
1. 101 Ahmad Faisal Jl. Merdeka No. 10, Jakarta [Edit] [Delete]
Apa yang Terjadi di Balik Layar:
nim=101&nama=Ahmad+Faisal&alamat=Jl.+Merdeka+No.+10%2C+Jakarta/mahasiswa/add dengan data di bodyapp.post("/mahasiswa/add", ...){ nim: "101", nama: "Ahmad Faisal", alamat: "Jl. Merdeka No. 10, Jakarta" }req.bodyINSERT INTO mahasiswa (nim, nama, alamat) VALUES ("101", "Ahmad Faisal", "Jl. Merdeka No. 10, Jakarta")HTTP 302 Found, Location: //SELECT * FROM mahasiswaCek Database: Bisa cek isi database menggunakan SQLite browser atau command:
sqlite3 mahasiswa.db "SELECT * FROM mahasiswa;"
102Siti NurhalizaJl. Sudirman No. 25, BandungNo NIM Nama Alamat Aksi
1. 101 Ahmad Faisal Jl. Merdeka No. 10, Jakarta [Edit] [Delete]
2. 102 Siti Nurhaliza Jl. Sudirman No. 25, Bandung [Edit] [Delete]
Observasi:
noUrut)
Apa yang Terjadi di Balik Layar:
/mahasiswa/edit/101req.params.nim = "101"SELECT * FROM mahasiswa WHERE nim = "101"{ nim: "101", nama: "Ahmad Faisal", alamat: "Jl. Merdeka No. 10, Jakarta" }mahasiswa/edit.hbsAhmad Faisal Rahman (tambahkan “Rahman”)
No NIM Nama Alamat Aksi
1. 101 Ahmad Faisal Rahman Jl. Merdeka No. 10, Jakarta [Edit] [Delete]
2. 102 Siti Nurhaliza Jl. Sudirman No. 25, Bandung [Edit] [Delete]
Apa yang Terjadi di Balik Layar:
/mahasiswa/edit/101req.params.nim = "101"req.body = { nama: "Ahmad Faisal Rahman", alamat: "Jl. Merdeka No. 10, Jakarta" }UPDATE mahasiswa SET nama = "Ahmad Faisal Rahman", alamat = "Jl. Merdeka No. 10, Jakarta" WHERE nim = "101"/Kenapa Redirect?

Apakah Anda yakin ingin menghapus data ini?
[OK] [Cancel]
JavaScript Confirmation:
onclick="return confirm('...')"No NIM Nama Alamat Aksi
1. 101 Ahmad Faisal Rahman Jl. Merdeka No. 10, Jakarta [Edit] [Delete]
Apa yang Terjadi di Balik Layar:
/mahasiswa/delete/102req.params.nim = "102"DELETE FROM mahasiswa WHERE nim = "102"/Catatan Penting:
| Kolom | Tipe Data | Constraint |
|---|---|---|
| nim | TEXT | PRIMARY KEY |
| nama | TEXT | NOT NULL |
| alamat | TEXT | NOT NULL |
mahasiswa.db akan otomatis dibuat di folder project saat pertama kali aplikasi dijalankannpm run dev, server akan otomatis restart setiap kali ada perubahan koders di terminal dan Enternodemon.json dengan konfigurasireq.body akan undefinedexpress.urlencoded({ extended: false })express.json()noUrut digunakan untuk penomoran baris di tabelhelpers: {
formatDate: (date) => new Date(date).toLocaleDateString('id-ID'),
uppercase: (str) => str.toUpperCase()
}
confirm() untuk konfirmasi sebelum menghapus data✅ Create - Menambah data mahasiswa baru
✅ Read - Menampilkan list data mahasiswa
✅ Update - Mengubah data mahasiswa
✅ Delete - Menghapus data mahasiswa
✅ Validasi Form - Input required
✅ Konfirmasi Delete - Mencegah penghapusan tidak sengaja
✅ Responsive Design - Menggunakan Bootstrap 5
✅ Auto Numbering - Penomoran otomatis di tabel
Selamat! Anda telah berhasil membuat aplikasi CRUD sederhana menggunakan:
Aplikasi ini mendemonstrasikan konsep dasar:
:nim)Konsep Penting yang Sudah Dipelajari:
Next Steps untuk Pembelajaran:
Selamat! Anda sudah memahami dasar-dasar pembuatan web aplikasi dengan Node.js! 🎉
Tips: