blog

Tutorial Membuat Aplikasi CRUD Data Mahasiswa Menggunakan Express.js, Handlebars, dan SQLite

📋 Daftar Isi

  1. Persiapan dan Instalasi
  2. Inisialisasi Project
  3. Instalasi Dependencies
  4. Membuat Struktur Folder
  5. Membuat File Utama (index.js)
  6. Membuat Layout Template
  7. Membuat Halaman Utama
  8. Membuat Halaman Tambah Data
  9. Membuat Halaman Edit Data
  10. Menjalankan Aplikasi
  11. Testing Aplikasi

1. Persiapan dan Instalasi

Prasyarat

Pastikan Anda telah menginstal:

Cek Instalasi Node.js

Buka terminal/command prompt dan jalankan:

node --version
npm --version

Penjelasan Command:

Screenshot 1: Terminal menampilkan versi Node.js dan npm

$ node --version
v18.17.0
$ npm --version
9.6.7

2. Inisialisasi Project

Langkah 2.1: Membuat Folder Project

mkdir pertemuan-2
cd pertemuan-2

Penjelasan Command:

Langkah 2.2: Inisialisasi npm

npm init -y

Penjelasan Command:


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:

Langkah 3.2: Install Development Dependencies

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.

Langkah 3.3: Modifikasi package.json

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:


4. Membuat Struktur Folder

Langkah 4.1: Membuat Folder Views

mkdir views
mkdir views/layouts
mkdir views/mahasiswa

Penjelasan struktur folder:

Pemisahan folder ini mengikuti prinsip separation of concerns - memisahkan file berdasarkan fungsinya agar lebih terorganisir.

Struktur Folder Akhir:

pertemuan-2/
├── index.js
├── package.json
├── views/
│   ├── index.hbs
│   ├── layouts/
│   │   └── main.hbs
│   └── mahasiswa/
│       ├── add.hbs
│       └── edit.hbs

5. Membuat File Utama (index.js)

Langkah 5.1: Membuat File index.js

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");
});

Penjelasan Kode index.js:

1. Import Modules

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:

2. Inisialisasi Database

const 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:

Catatan: SQLite hanya memiliki beberapa tipe data: NULL, INTEGER, REAL, TEXT, BLOB. Dalam praktiknya TEXT bisa menyimpan string dengan panjang apapun.

3. Konfigurasi Handlebars

const hbs = engine({
  extname: "hbs",
  defaultLayout: "main",
  layoutsDir: path.join(__dirname, "views", "layouts"),
  helpers: {
    noUrut: (index) => index + 1,
  },
});

Penjelasan konfigurasi:

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.

4. Konfigurasi Express

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:

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.

5. Routes

Route GET ‘/’ - Halaman Utama

app.get("/", (req, res) => {
  const mahasiswa = db.prepare("SELECT * FROM mahasiswa").all();
  res.render("index", { mahasiswa });
});

Penjelasan step by step:

Route GET ‘/mahasiswa/add’ - Halaman Tambah Data

app.get("/mahasiswa/add", (req, res) => {
  res.render("mahasiswa/add");
});

Penjelasan:

Route 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:

Route 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:

Route 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:

Catatan Keamanan: Dalam aplikasi production, sebaiknya tambahkan konfirmasi di backend dan authorization untuk memastikan user berhak menghapus data tersebut.

6. Start Server

app.listen(3000, () => {
  console.log("Server is running on http://localhost:3000");
});

Penjelasan:

Cara akses: Buka browser dan ketik http://localhost:3000


6. Membuat Layout Template

Langkah 6.1: Membuat File main.hbs

Buat file views/layouts/main.hbs:

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Program Input Data Mahasiswa</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <div class="container">
      }
    </div>
  </body>
</html>

Penjelasan:

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:

Bagaimana cara kerjanya? Ketika kita melakukan res.render("index", data), Express-Handlebars akan:

  1. Render index.hbs dengan data yang diberikan
  2. Masukkan hasil render ke dalam main.hbs menggantikan }
  3. Kirim HTML lengkap ke browser

7. Membuat Halaman Utama

Langkah 7.1: Membuat File index.hbs

Buat file views/index.hbs:

<h1>List Data Mahasiswa</h1>
<hr />
<a href="/mahasiswa/add" class="btn btn-primary mb-3">Tambah Data</a>
<table class="table table-bordered">
  <thead>
    <tr>
      <th>No</th>
      <th>NIM</th>
      <th>Nama</th>
      <th>Alamat</th>
      <th>Aksi</th>
    </tr>
  </thead>
  <tbody>
    
      <tr>
        <td>.</td>
        <td></td>
        <td></td>
        <td></td>
        <td>
          <a
            href="/mahasiswa/edit/"
            class="btn btn-warning"
          >Edit</a>
          <form
            action="/mahasiswa/delete/"
            method="POST"
            style="display: inline-block;"
          >
            <button
              type="submit"
              class="btn btn-danger"
              onclick="return confirm('Apakah Anda yakin ingin menghapus data ini?')"
            >
              Delete</button>
          </form>
        </td>
      </tr>
    
  </tbody>
</table>

Penjelasan:

Struktur Halaman:

Button Tambah Data:

Tabel Data:

Handlebars Syntax:

Button Edit:

Form Delete:

Catatan Penting:


8. Membuat Halaman Tambah Data

Langkah 8.1: Membuat File add.hbs

Buat file views/mahasiswa/add.hbs:

<h1>Tambah Data Mahasiswa</h1>
<hr />
<form action="/mahasiswa/add" method="POST">
  <div class="mb-3">
    <label for="nim" class="form-label">NIM</label>
    <input type="text" class="form-control" id="nim" name="nim" required />
  </div>
  <div class="mb-3">
    <label for="nama" class="form-label">Nama</label>
    <input type="text" class="form-control" id="nama" name="nama" required />
  </div>
  <div class="mb-3">
    <label for="alamat" class="form-label">Alamat</label>
    <input
      type="text"
      class="form-control"
      id="alamat"
      name="alamat"
      required
    />
  </div>
  <button type="submit" class="btn btn-primary">Simpan</button>
</form>

Penjelasan:

Form HTML:

Structure Form Bootstrap:

Tiga Input Fields:

  1. NIM: Nomor Induk Mahasiswa (akan jadi primary key)
  2. Nama: Nama lengkap mahasiswa
  3. Alamat: Alamat mahasiswa

Button Submit:

Flow Submit Form:

  1. User isi form dan klik “Simpan”
  2. Browser validasi (required fields tidak boleh kosong)
  3. Browser kirim POST request ke /mahasiswa/add
  4. Data dikirim sebagai URL-encoded: nim=101&nama=Ahmad&alamat=Jakarta
  5. bodyParser middleware di Express meng-parse menjadi object: { nim: "101", nama: "Ahmad", alamat: "Jakarta" }
  6. Data tersedia di req.body di route handler
  7. Route handler insert data ke database dan redirect

Kenapa tidak pakai GET? GET tidak cocok untuk form karena:


9. Membuat Halaman Edit Data

Langkah 9.1: Membuat File edit.hbs

Buat file views/mahasiswa/edit.hbs:

<h1>Edit Data Mahasiswa</h1>
<hr />
<form action="/mahasiswa/edit/" method="POST">
  <div class="mb-3">
    <label for="nim" class="form-label">NIM</label>
    <input
      type="text"
      class="form-control"
      id="nim"
      name="nim"
      required
      value=""
      readonly
    />
  </div>
  <div class="mb-3">
    <label for="nama" class="form-label">Nama</label>
    <input
      type="text"
      class="form-control"
      id="nama"
      name="nama"
      required
      value=""
    />
  </div>
  <div class="mb-3">
    <label for="alamat" class="form-label">Alamat</label>
    <input
      type="text"
      class="form-control"
      id="alamat"
      name="alamat"
      required
      value=""
    />
  </div>
  <button type="submit" class="btn btn-primary">Simpan</button>
</form>

Penjelasan:

Perbedaan dengan add.hbs:

  1. Form Action Dinamis:
    • action="/mahasiswa/edit/": URL action yang mengandung NIM
    • Contoh: jika nim = “101”, action jadi /mahasiswa/edit/101
    • NIM diambil dari data mahasiswa yang di-pass dari controller
  2. Input dengan Value:
    • value="": Mengisi input dengan data yang sudah ada
    • value="": Form terisi otomatis (pre-filled)
    • value="": User bisa langsung edit tanpa ketik ulang
  3. NIM Read-only:
    • <input ... value="" readonly />: NIM tidak bisa diubah
    • Attribute readonly membuat input tidak bisa di-edit (greyed out)
    • Kenapa read-only? Karena NIM adalah PRIMARY KEY di database
    • Primary key tidak boleh diubah karena digunakan sebagai identifier unik
    • Jika perlu ubah NIM, seharusnya delete data lama dan create data baru
  4. Nama dan Alamat Editable:
    • Input nama dan alamat tidak readonly, bisa diubah
    • User tinggal modifikasi text yang sudah ada

Flow Edit Data:

  1. User klik button “Edit” di halaman utama
  2. Browser request GET /mahasiswa/edit/101
  3. Controller query database berdasarkan NIM
  4. Controller render edit.hbs dengan data mahasiswa
  5. Form muncul dengan fields sudah terisi
  6. User ubah nama/alamat dan klik “Simpan”
  7. Browser submit POST /mahasiswa/edit/101 dengan data baru
  8. Controller update database WHERE nim = ?
  9. Controller redirect ke halaman utama
  10. User melihat data yang sudah terupdate

Kenapa 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.


10. Menjalankan Aplikasi

Langkah 10.1: Menjalankan Server

Buka terminal di folder project dan jalankan:

npm run dev

Penjelasan:

Alternative 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:

Troubleshooting:

Langkah 10.2: Buka Browser

Buka browser dan akses:

http://localhost:3000

Penjelasan URL:

Yang Akan Muncul:

Catatan:


11. Testing Aplikasi

Test 1: Menambah Data Pertama

Langkah 11.1: Klik Tombol “Tambah Data”

Apa yang Terjadi di Balik Layar:

  1. Browser mengirim GET request ke /mahasiswa/add
  2. Express matching request dengan route: app.get("/mahasiswa/add", ...)
  3. Route handler render template mahasiswa/add.hbs
  4. Handlebars compile template dan inject ke layout main.hbs
  5. HTML lengkap dikirim ke browser
  6. Browser render HTML dan tampilkan form kosong

Langkah 11.2: Isi Form

Tips Pengisian:

Langkah 11.3: Klik “Simpan”

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:

  1. Browser validasi form (semua required field terisi?)
  2. Browser serialize form data: nim=101&nama=Ahmad+Faisal&alamat=Jl.+Merdeka+No.+10%2C+Jakarta
  3. Browser kirim POST request ke /mahasiswa/add dengan data di body
  4. Express routing: match dengan app.post("/mahasiswa/add", ...)
  5. bodyParser middleware parse data jadi object: { nim: "101", nama: "Ahmad Faisal", alamat: "Jl. Merdeka No. 10, Jakarta" }
  6. Route handler ambil data dari req.body
  7. Execute SQL: INSERT INTO mahasiswa (nim, nama, alamat) VALUES ("101", "Ahmad Faisal", "Jl. Merdeka No. 10, Jakarta")
  8. Data tersimpan di database (file mahasiswa.db bertambah ukurannya)
  9. Server respond dengan redirect: HTTP 302 Found, Location: /
  10. Browser otomatis follow redirect, kirim GET request ke /
  11. Route handler query database: SELECT * FROM mahasiswa
  12. Hasil query (array dengan 1 item) di-pass ke template
  13. Template render tabel dengan data
  14. Browser tampilkan halaman utama dengan data baru

Cek Database: Bisa cek isi database menggunakan SQLite browser atau command:

sqlite3 mahasiswa.db "SELECT * FROM mahasiswa;"

Test 2: Menambah Data Kedua

Langkah 11.4: Tambah data lagi

No  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:

Test 3: Edit Data

Langkah 11.5: Klik “Edit” pada data pertama

Apa yang Terjadi di Balik Layar:

  1. Browser kirim GET request ke /mahasiswa/edit/101
  2. Express routing extract parameter: req.params.nim = "101"
  3. Route handler query database: SELECT * FROM mahasiswa WHERE nim = "101"
  4. Database return 1 row: { nim: "101", nama: "Ahmad Faisal", alamat: "Jl. Merdeka No. 10, Jakarta" }
  5. Data di-pass ke template mahasiswa/edit.hbs
  6. Template render form dengan value pre-filled
  7. Input NIM readonly (tidak bisa diubah)
  8. Browser tampilkan form

Langkah 11.6: Ubah Nama

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:

  1. User ubah nama dan klik “Simpan”
  2. Browser kirim POST request ke /mahasiswa/edit/101
  3. Express routing extract: req.params.nim = "101"
  4. bodyParser parse form: req.body = { nama: "Ahmad Faisal Rahman", alamat: "Jl. Merdeka No. 10, Jakarta" }
  5. Note: NIM tidak ada di req.body karena input readonly (tapi tetap visible di form)
  6. Route handler execute SQL: UPDATE mahasiswa SET nama = "Ahmad Faisal Rahman", alamat = "Jl. Merdeka No. 10, Jakarta" WHERE nim = "101"
  7. SQLite update 1 row di database
  8. Server respond dengan redirect ke /
  9. Browser follow redirect dan tampilkan data terbaru
  10. Nama sudah berubah dari “Ahmad Faisal” jadi “Ahmad Faisal Rahman”

Kenapa Redirect?

Test 4: Hapus Data

Langkah 11.7: Klik “Delete” pada data kedua

Apakah Anda yakin ingin menghapus data ini?
[OK] [Cancel]

JavaScript Confirmation:

Langkah 11.8: Klik “OK”

No  NIM   Nama                  Alamat                         Aksi
1.  101   Ahmad Faisal Rahman   Jl. Merdeka No. 10, Jakarta   [Edit] [Delete]

Apa yang Terjadi di Balik Layar:

  1. User klik “OK” di confirmation dialog
  2. Browser submit form: POST request ke /mahasiswa/delete/102
  3. Express routing extract: req.params.nim = "102"
  4. Route handler execute SQL: DELETE FROM mahasiswa WHERE nim = "102"
  5. SQLite hapus 1 row dari database
  6. File mahasiswa.db size berkurang
  7. Server respond dengan redirect ke /
  8. Browser follow redirect dan query data terbaru
  9. Data dengan NIM 102 (Siti Nurhaliza) sudah tidak ada
  10. Penomoran otomatis adjust: yang tadinya row 1 tetap row 1

Catatan Penting:


🗂️ Struktur Database

Tabel: mahasiswa

Kolom Tipe Data Constraint
nim TEXT PRIMARY KEY
nama TEXT NOT NULL
alamat TEXT NOT NULL


📝 Catatan Penting

  1. SQLite Database:
    • File mahasiswa.db akan otomatis dibuat di folder project saat pertama kali aplikasi dijalankan
    • Database tersimpan dalam satu file, mudah untuk backup (tinggal copy file)
    • Cocok untuk development dan aplikasi kecil
    • Untuk production dengan traffic tinggi, pertimbangkan MySQL/PostgreSQL
  2. Nodemon:
    • Saat menggunakan npm run dev, server akan otomatis restart setiap kali ada perubahan kode
    • Nodemon memonitor file dengan extension: .js, .mjs, .cjs, .json
    • Untuk manual restart saat nodemon running, ketik rs di terminal dan Enter
    • Jika ingin ignore certain files, buat file nodemon.json dengan konfigurasi
  3. Body Parser:
    • Middleware ini diperlukan untuk membaca data dari form HTML
    • Tanpa bodyParser, req.body akan undefined
    • Starting Express 4.16.0, bisa pakai built-in: express.urlencoded({ extended: false })
    • Untuk JSON data, gunakan express.json()
  4. Handlebars Helpers:
    • Helper noUrut digunakan untuk penomoran baris di tabel
    • Kita bisa membuat helper lain seperti: formatDate, capitalize, ifEqual, dll
    • Helper sangat berguna untuk logic sederhana yang sering dipakai
    • Example custom helper:
      helpers: {
        formatDate: (date) => new Date(date).toLocaleDateString('id-ID'),
        uppercase: (str) => str.toUpperCase()
      }
      
  5. Primary Key:
    • NIM dijadikan primary key sehingga tidak boleh ada NIM yang sama
    • Jika insert NIM duplikat, SQLite akan throw error
    • Primary key automatically indexed untuk query cepat
    • Sebaiknya primary key tidak pernah diubah setelah dibuat
  6. Readonly Input:
    • Input NIM di halaman edit dibuat readonly karena primary key tidak boleh diubah
    • Alternative: gunakan hidden input dan tampilkan NIM sebagai text biasa (non-editable)
    • Readonly input masih terkirim saat form submit (berbeda dengan disabled)
  7. Confirmation Dialog:
    • Menggunakan JavaScript confirm() untuk konfirmasi sebelum menghapus data
    • Ini adalah browser native dialog, tidak terlalu bagus secara UI/UX
    • Untuk aplikasi production, gunakan modal confirmation yang lebih menarik (e.g., Bootstrap Modal, SweetAlert)
    • Dialog confirm hanya protection di client-side, server tetap harus validate
  8. HTTP Methods:
    • GET untuk menampilkan halaman (read-only, idempotent, bisa di-bookmark)
    • POST untuk mengirim data (create, update, delete) - tidak idempotent
    • GET request bisa di-cache oleh browser, POST tidak
    • RESTful best practice:
      • POST untuk CREATE
      • PUT/PATCH untuk UPDATE
      • DELETE untuk DELETE
    • Tapi karena HTML form hanya support GET & POST, kita pakai POST untuk semua operasi yang mengubah data
  9. Security Considerations:
    • Aplikasi ini masih sangat basic, tidak ada authentication/authorization
    • Prepared statements melindungi dari SQL injection
    • Tidak ada validation di backend (hanya HTML5 validation)
    • Tidak ada error handling (try-catch)
    • Untuk production, perlu tambahan:
      • Input validation & sanitization
      • CSRF protection
      • Rate limiting
      • Proper error handling
      • Logging
  10. Database Connection:
    • SQLite connection synchronous (blocking)
    • Untuk production, gunakan database dengan async driver (MySQL, PostgreSQL)
    • Atau gunakan ORM seperti Sequelize, Prisma untuk abstraction lebih baik

🎯 Fitur Aplikasi

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


📚 Teknologi yang Digunakan


🎓 Kesimpulan

Selamat! Anda telah berhasil membuat aplikasi CRUD sederhana menggunakan:

Aplikasi ini mendemonstrasikan konsep dasar:

Konsep Penting yang Sudah Dipelajari:

  1. Request-Response Cycle: Bagaimana browser berkomunikasi dengan server
  2. Middleware: Function yang memproses request sebelum sampai ke route handler
  3. Template Rendering: Mengubah data menjadi HTML menggunakan template engine
  4. Database CRUD: Create, Read, Update, Delete operations
  5. RESTful Routing: Penamaan route yang konsisten dan meaningful
  6. Separation of Concerns: Memisahkan logic, data, dan presentation

Next Steps untuk Pembelajaran:

Selamat! Anda sudah memahami dasar-dasar pembuatan web aplikasi dengan Node.js! 🎉

Tips: