Skip to content

Cerita koding ku #2

Project : Membuat backend aplikasi login dengan Express.JS

Introduction

Pada perjalanan coding ini, saya mencoba membuat aplikasi (backend) menggunakan Express.js (framework Node.js). Pilihan saya jatuh pada MySQL sebagai basis data dan Prisma sebagai ORM, ditambah penggunaan JWT untuk meningkatkan keamanan. Aplikasi yang akan dibangun sangat sederhana, fokus pada proses registrasi, login, serta menampilkan informasi pengguna.

Goals

  • Penyimpanan data user dengan enkripsi menggunakan bcrypt.
  • Pengautentikan user berbasis JWT.
  • Eksperimen dengan Prisma sebagai Object Relational Mapping (ORM).
  • Memastikan keamanan Route untuk membatasi akses hanya pada user terotentikasi.
  • Menggunakan Postman untuk testing API.
  • Mendokumentasikan pengalaman saya untuk membantu diri saya sendiri dan orang lain yang ingin mempelajarinya.

Steps

#1 Inisialisasi project

Saya menggunakan command prompt dan mengarahkan ke lokasi yang saya inginkan untuk menginstal project, bagi yang tidak tau terminal itu semacam aplikasi yang digunakan untuk menuliskan perintah kalau di windows namanya Command Prompt biasa di singkat CMD.

Sebelumnya perlu diketahu bahwa di device yang saya gunakan telah terinstall node versi v18.16.0 dan npm versi 9.6.7

Saya memberi nama project ini dengan basic-login-express, kalian bisa bebas memberi nama projectnya dan lokasi yang kalian pilih untuk projectnya.

Saya akan menjelaskan barisan perintah yang ada pada gambar diatas mulai dari baris ke #4.

baris ke #4

Saat pertama kali CMD di buka akan selalu mengarah ke Drive C, sementara saya ingin membuat project di drive D pada folder React. Sehingga saya mengetikan cd /D D:\React untuk mengubah Drive dari C ke D dan langsung mengarah ke folder React. Jika kalian ketikan cd D:\React itu tidak akan berfungsi karna kalian masih di drive C jadi kalian harus berpindah ke drive D terlebih dahulu.

baris ke #6

Saya membuat folder bernama basic-login-express dengan menggunakan perintah mkdir <nama folder>.

baris ke #7

Saya masuk ke folder yang baru saja saya buat yaitu folder basic-login-express.

baris ke #9

Saya menggunakan perintah `npm init -y` untuk memulai proyek dengan cepat. Ini adalah cara singkat untuk menginisialisasi proyek tanpa harus menjawab banyak pertanyaan. Biasanya, saat Anda menjalankan `npm init`, Anda akan diminta untuk memberikan informasi seperti nama proyek, versi, deskripsi, dan lainnya. Namun, dengan menambahkan `-y`, Anda secara otomatis menerima nilai default untuk pertanyaan-pertanyaan tersebut. Selain itu, perintah ini juga membuat berkas `package.json` yang berisi konfigurasi dasar proyek Anda. Dengan demikian, Anda dapat mulai bekerja pada proyek Anda dengan cepat dan efisien.

baris ke #28

Saya menginstal beberapa package diatara lain adalah express, prisma, prisma client, bcrypt dan jsonwebtoken. untuk kegunaan package-package tersebut akan saya jelaskan nanti.

Setelah menjalankan perintah yang telah saya sebutkan sebelumnya, saya membuka proyek menggunakan Visual Studio Code. Di gambar di atas, Anda dapat melihat hasil dari inisialisasi proyek. Terdapat folder node_module yang berisi semua paket yang telah saya instal. File package-lock.json dihasilkan secara otomatis oleh npm saat Anda menginstal paket. File ini bertujuan untuk mengunci versi dependensi dan sub-dependensinya yang diinstal, memastikan bahwa semua orang yang bekerja pada proyek ini menggunakan versi yang sama. Sementara itu, file package.json adalah file konfigurasi dasar dalam proyek Node.js. Di dalamnya terdapat metadata tentang proyek, daftar dependency, serta pengaturan lainnya.

#2 Pengaturan Environment

Saya membuat file environment dengan cara membuat file bernama .env di folder utama, yang juga dikenal sebagai "root folder" Jika Anda tidak familiar dengan istilah root folder itu adalah folder utama atau, secara sederhananya, tempat di mana Anda menempatkan file package.json. Berkaitan dengan fungsinya, file .env digunakan untuk menyimpan data-data yang bersifat rahasia, seperti kata sandi atau kunci enkripsi, koneksi ke basis data, dan lain sebagainya. Penting untuk diingat bahwa file ini sebaiknya tidak diunggah (push) ke repositori Git atau layanan kode bersama, karena mengandung informasi sensitif.

Saya menggunakan file tersebut untuk menyimpan URL database dan JWT_SECRET. URL database akan digunakan oleh Prisma sebagai alamat tujuan saat melakukan proses migrasi, sementara JWT_SECRET akan berfungsi sebagai kunci enkripsi untuk menghasilkan dan memverifikasi token JSON Web. Detailnya akan saya jelaskan lebih lanjut. Berikut ini adalah contoh isi dari file .env:

[code language="plaintext"]
# Konfigurasi aplikasi
# Alamat database
URL_DATABASE=mysql://root:@127.0.0.1:3306/gen_upd_ex
# JSON Web Token Secret
JWT_SECRET=7K9pXcE2RjT5uBhW
#Aplikasi akan berjalan pada port 3000
PORT=3000
[/code]

Pastikan untuk mengisi nilai yang sesuai dengan URL database dan JWT_SECRET yang Anda gunakan dalam proyek Anda. Ingatlah untuk menjaga kerahasiaan file .env ini dan tidak mengunggahnya ke repositori Git atau layanan berbagi kode jika project yang kalian buat bersifat private.

Mungkin anda akan sedikit bingung pada bagian URL_DATABASE namun akan saya jelaskan agar dapat dipahami dan pada dasarnya ini adalah sebuah string koneksi,


[code language="plaintext"]
# mysql di sini berarti saya menggunakan database MySQL 
# root adalah user yang digunakan untuk login ke MySQL.
# 127.0.0.1 adalah server local saya.
# 3306 adalah port dimana database saya berjalan.
# gen_upd_ex adalah nama database yang saya gunakan.
[/code]

#3 Pengaturan Prisma ORM

Kenapa saya perlu melakukan pengaturan untuk prisma?
Ya, karena saya ingin menggunakannya sebagai ORM dan itu perlu di settings, Jika anda familiar dengan Laravel maka seharusnya anda tidak asing dengan eloquent yang tidak seperti prisma yang harus di settings terlebih dahulu sebelum digunakan.


Hal pertama yang saya lakukan adalah membuat file bernama schema.prisma di root folder, Di dalam file tersebut nantinya akan berisi data model database atau struktur tabel pada database karena memang migrasi di buat berdasarkan file ini dan ketika migrasi di jalankan maka akan membuat tabel sesuai dengan data model yang telah di tulis dalam schema.prisma. Selain itu juga ada pengaturan untuk datasource yang meliputi type database atau provider dan url koneksi ke real database.


[code language="plaintext"]
datasource db {
    provider    = "mysql"
    url         = env("DATABASE_URL")
}


model User {
    id          Int         @id @default(autoincrement())
    username    String      @unique
    password    String
}

generator client {
  provider = "prisma-client-js"
}
[/code]

Pada bagian generator client itu adalah configurasi untuk prisma client, Prisma client sendiri berfungsi sebagai lapisan akses data yang memungkinkan saya untuk berinteraksi dengan database secara efisien jadi tidak perlu melakukan query seperti di MySQL. Selanjutnya ada provider = "prisma-client-js" yang menjelaskan ke prisma ketika membuat client itu dibuat dalam format JavaScript sehingga ketika berinteraksi dengan database nanti yaa bahasanya menggunakan JavaScript. Untuk membuat client cukup dengan perintah npx prisma generate tapi itu nanti dulu karena kita perlu membuat migrasi dan database realnya terlebih dahulu.


Untuk membuat migrasi cukup dengan jalankan perintah npx prisma migrate dev --name <nama migrasi> yang dalam case ini saya menggunakan perintah npx prisma migrate dev --name migrasi_pertama dan prisma akan otomatis membuat database berdasarkan migrasi.



Gambar dibawah ini adalah database yang telah dibuat dan perhatikan tabel user, tabel tersebut memiliki struktur yang sama dengan model User di schema.prisma. Untuk tabel _prisma_migrations itu adalah bawaan migrasi untuk menyimpan data migrasi apa saja yang telah di jalankan.



Seperti yang saya katakan tadi jika database memang dibuat berdasarkan schema.prisma namun tidak secara langsung melainkan lewat migrasi dan migrasi dibuat berdasarkan schema.prisma baru setelah itu tabel pada database di buat berdasarkan migrasi. Gambar dibawah adalah migrasi yang dibuat dengan perintah npx prisma migrate dev --name migrasi_pertama.



Nah semisal saya melakukan perubahan di schema.prisma dan saya ingin menerapkan perubahan tersebut maka saya hanya perlu melakukan menjalankan perintah npx prisma migrate dev --name <nama migrasi> untuk membuat migrasi baru dan menjalankan perintah npx prisma migrate up untuk menjalankan migrasi dan mengubah skema database sesuai dengan perubahan yang terjadi di schema.prisma.


#4 Management Folder Structure
Sebenarnya ini tidak perlu dilakukan di project kecil karena project kecil hanya berisi sedikit kode jadi akan mudah dipahami, Meskipun begitu saya selalu melakukan management folder sebagai bentuk disiplin seorang developer karena hal ini sangat penting dilakukan apalagi jika project semakin berkembang dan memiliki banyak fitur jadi tentu saya ingin melakukan maintenance dengan mudah.


Folder Structure

Saya mengatur project dengan sedemikian karena saya ingin memisahkan controller, route, middleware dan index.js.

Controllers : berfungsi untuk menyimpan file controller yang bertugas untuk memproses suatu logic aplikasi, berinteraksi dengan model sehingga dapat melakukan manipulasi data ke database, menghandel permintaan yang datang. Jadi dalam file ini berisi function-function untuk memproses suatu logic.

Routes : berfungsi untuk menyimpan file routes yang bertugas untuk membuat suatu jalur dalam bentuk URL dan jalur tesebut akan dihubungkan ke fucntion yang ada di dalam controller. Saat URL diakses juga bisa mengirimkan suatu data dan data tersebut akan di proses oleh controller setelah itu controller memberi respon.

MiddleWares : Folder ini akan berisi file-file middleware yang bertugas sebagai penengah antara controller dan route, Jadi ketika ada orang yang mengakses URL dari route tidak langsung dilanjutkan ke controller melainkan akan di proses terlebih dahulu di middleware baru kemudian dilanjutkan ke controller. Biasanya ini digunakan untuk memproteksi suatu route agar tidak sembarang orang bisa akses.

Index.js : digunakan untuk inisialisasi project, pengaturan project seperti akan berjalan di port berapa dan melakukan import komponen atau file agar project dapat berjalan dengan fungsi yang telah di import.


#5 Menyiapkan authController.js
File ini akan berisi 3 function yaitu register, login, getProfile


[code language="plaintext"]
const bcrypt = require('bcrypt'); // memuat modul bcrypt dan menampung ke variable bcrypt
const jwt = require('jsonwebtoken'); // memuat module jsonwebtoken dan menampung ke variable jwt
const { PrismaClient } = require('@prisma/client'); // destructuring assignment untuk mengimpor kelas PrismaClient dari modul @prisma/client
const prisma = new PrismaClient(); // membuat instance dari kelas PrismaClient

const register = async (req, res) =&amp;amp;gt; {
    const {username, password} = req.body; // destructuring assignment

    try {
        hashedPassword = await bcrypt.hash(password,10); // melakukan hashing password dengan cost factor 10, semakin tinggi cost factor akan semakin lama operasi hashing dilakukan
        const user = await prisma.user.create({
            data: {
                username,
                password: hashedPassword,
            }
        });

        res.json({message : 'User registered successfully', user});
    } catch (error) {
        res.status(500).json({message: 'Error registering user'});
    }
}

const login = async (req, res) =&amp;amp;gt; {
    const { username, password } = req.body;

    try {
        const user = await prisma.user.findUnique({ where: { username } });
        if (!user) {
            return res.status(404).json({ message: "User not found!" });
        }

        const passwordMatch = await bcrypt.compare(password, user.password);

        if (!passwordMatch) {
            return res.status(401).json({ message: 'Invalid password' });
        }

        // Create a JWT token
        const token = jwt.sign({ username: user.username }, process.env.JWT_SECRET);

        // Return the JWT token as a response
        res.json({ message: 'Login successful', token });
    } catch (error) {
        res.status(500).json({ message: 'Error logging in' });
    }
}

const getProfile = async (req, res) =&amp;amp;gt; {
    res.json({ message: `Welcome to your profile!` });
}

module.exports = {
    register,
    login,
    getProfile,
};
[/code]

Saya akan menjelaskan kode diatas dengan menggunakan nomor baris.

#3 Destructuring Assignment
Ini adalah fitur dari JavaScript yang memungkinkan untuk mengambil nilai atau class dari suatu object dalam case ini adalah @prisma/client dan menyimpan ke variable yang di siapkan. Karena @prisma/client ketika di muat akan menghasilkan banyak class dan saya hanya membutuhkan 1 class yaitu PrismaClient maka saya melakukan Destructuring Assignment dengan cara const { PrismaClient } = require('@prisma/client');.

#6 Membuat Function const register = async (req, res) => {}
kata async menandakan operasi yang asynchronous (tidak langsung berurutan). (req, res) => adalah arrow function dengan 2 parameter yaitu req atau request dan res atau response, kemudian {} adalah tubuh dari function yang berisi logic dari sebuah function dan dalam function ini di mulai di baris 6 dan selesai di baris 22, Sementara const register = adalah proses menampung sebuah function ke variable register ini dilakukan agar nanti variable ini dapat di export dari file authController.js sehigga dapat di gunakan di authRoutes.js.

#10 Operasi Hashing
Dikarenakan nantinya table user akan menyimpan password maka sebelum di simpan harus dihashing terlebih dahulu menggunakan bcrypt, jadi semisal password plain text nya adalah 1 ketika di hashing akan menjadi $2b$10$gU5w/GJc57j5nx0z1Lt1XucSZC.MUxf5LRgk5msfDYCFKsi3qPuP6 sehingga meskipun orang dapat mengakses database dan melihat ke dalam table user, Orang tersebut tetap tidak tau password yang sesungguhnya.

#11 Proses membuat data dengan Prisma
Karena saya sudah memuat Class PrismaClient dan meampung ke variable prisma maka prisma di sini juga mengandung model user yang ada di schema.prisma sehingga ketika saya ingin menambah data cukup menggunakan kode prisma.user.create({ }); di tengah { } adalah tempat data yang ingin di simpan, Jadi bari ke 12 sampai 15 adalah data yang akan di simpan.

#18 Merespone
Setelah data berhasil di buat maka function akan mengembalikan respon dengan pesan dan menyertakan data yang baru saja di buat, Respon ini di berikan dengan format JSON.

#20 Pemberitahuan Error
Jika terjadi kegagalan dalam proses pembuatan data atau proses yang berada di dalam try { } maka function akan merenspon dengan memberikan pesan error dalam format JSON.

#28 – 31 Mencari Data Yang Cocok
Proses pencarian data dilakukan mengunakan function bawaan prisma yaitu findUnique, Jika ada data username yang cocok maka proses logic akan berlanjut tetapi jika tidak maka variable user akan kosong sehingga akan masuk dalam logic if(!user) dan proses akan logic akan berakhir sebelum semua logic di jalankan.

#33 Pencocokan Password
Karena data yang tersimpan telah di hashing maka perlu dilakukan pencocokan karena user tidak akan mengisikan password yang telah di hashing ketika login. Proses ini dapat dilakukan dengan menggunakan bcrypt.compare(password, user.password), password adalah parameter sementara user.password adalah password dari database yang telah di hashing. Ingat di baris 28 – 31 jika data di temukan maka seluruh row data yang cocok akan di ambil dan di tampung ke variable user sehingga ketika ingin mengambil data password dari table user maka cukup dengan user.password.

#40 Membuat token
jwt.sign({payload, secretOrPrivateKey}) adalah cara untuk membuat unique token yang nanti akan digunakan untuk keperluan middleware, payload di isi dengan username sementara PrivateKey mengunakan data JWT_SECRET dari file .env yaitu 1n1R4h4514.

#53 – 57 Proses Export
Ini adalah cara mengexport ke 3 function yang telah di buat untuk dapat digunakan di file lain yang membutuhkan dalam case ini adalah file authRoutes.js.


#6 Menyiapkan authRoutes.js
File ini nanti yang akan membuat function di controller dapat diakses melalui URL. Karena dalam baris kode sudah ada penjelasan jadi tidak akan saya jelaskan kembali.


[code language="plaintext"]
// memuat express module.
const express = require('express');

// memuat authController agar function di dalamnya dapat di panggil di sini.
const authController = require('../controllers/authController');

// memuat function Router dari express agar dapat mengunakan routing bawaan express.
const router = express.Router();

// membuat route dengan nama register dan mengarahkan ke function register yang berada di authController.js
// semisal ingin membuat data baru maka harus mengakses http://localhost:3000/register denggan http method post.
router.post('/register', authController.register);
router.post('/login', authController.login);
// sama saja dengan route di atas tapi menggunakan http method get.
router.get('/profile', authController.getProfile);

// mengexport router agar dapat di panggil oleh index.js
module.exports = router;

[/code]

#7 Menyiapkan index.js
File ini nanti yang akan menjadi starting point dari project ini.


[code language="plaintext"]
const express = require('express');

// memuat module body-parser.
const bodyParser = require('body-parser');

// memuat file authRoutes.js agar route yang telah di siapkan di file tersebut dapat digunakan di sini.
const authRoutes = require('./routes/authRoutes'); // Path to your authRoutes
const app = express();

// mengambil nilai PORT dari file .env atau bila kosong maka PORT akan bernilai 3000.
const PORT = process.env.PORT || 3000;

// Menggunakan bodyParser untuk membaca data dari body permintaan untuk data dalam format JSON.
// karna nanti saat testing menggunakan postman raw body json jadi ini perlu di setting.
app.use(bodyParser.json());

// app express mengunakan authRoutes sehingga route yang telah disiapkan dapat diakses.
app.use(authRoutes);

// menjalankan app ke PORT.
app.listen(PORT, () =&amp;amp;gt; {
    console.log(`Server running on port ${PORT}`);
});

[/code]

#8 Testing
Sebelum melakukan testing ada beberapa hal yang perlu dilakukan salah satunya yaitu merubah starting point program dengan cara buka file package.json cari code "test": "echo \"Error: no test specified\" && exit 1" dan rubah menjadi "start": "node index.js" kemudian jalankan perintah npx prisma generate di terminal VS Code, Setelah itu jalankan perintah npm start.


Seharusnya muncul informasi seperti ini

Karena project yang saya buat hanya backend atau web service yang tidak memiliki tampilan maka untuk melakukan pengujian saya menggunakan Postman.


Postman

Saya membuat Collection bernama Basic Login Ex dan pada bagian variable saya membuat variable prefix yang berisi URL dan Port menjadi http://localhost:3000/ itu karena project saya akan berjalan di localhost dan menggunakan port 3000. Kemudian membuat folder bernama auth dan membuat 2 request dengan metode POST dan 1 request dengan metode GET.


Register Request

{{prefix}}register atau http://localhost:3000/register itu adalah url yang telah saya settings di file authRoutes.js dan sekarang tinggal melakukan testing pada route atau request ini, hasil nya ada pada gambar di bawah.


Hasil Testing Register

Ketika tombol send di klik maka request tersebut akan dikirim dan request tersebut di kirimkan ke route register dengan data username beserta password selanjutnya route akan meneruskan ke controller dan di controller request ini akan di proses dengan membuat data user baru dan memberikan respon bahwa data berhasil di buat beserta data yang baru saja di buat.


Login Request

Prosesnya sama dengan contoh yang sebelumnya hanya saja ketika username di temukan dan password cocok maka controller akan memberi sebuah token dan token ini lah yang harus di kirimkan di setiap request ke route yang diproteksi dan hal itu akan di lakukan oleh middleware.


Profile Request

Karena route ini belum di proteksi maka cukup dengan melakukan request tanpa ada data apapun yang dikirim maka controller akan menampilkan data dan nantinya saya akan memproteksi route ini.


#9 Pemasangan Middleware
Lakukan perubahan code pada file authMiddleware.js


[code language="plaintext"]
// memuat module jsonwebtoken
const jwt = require('jsonwebtoken');

const authMiddleware = (req, res, next) =&amp;amp;gt; {
    // mengambil value token dari header dengan key x-auth-token
    const token = req.header('x-auth-token');

    // melakukan pemeriksaan apa bila token tidak di sertakan
    if (!token){
        return res.status(401).json({error: 'No token, authorization denied'});
    }

    try {
        // proses pemeriksaan apakah token valid, pemeriksaan mengunakan function bawaan jwt yaitu verify
        const decoded = jwt.verify(token, process.env.JWT_SECRET);

        // mengambil data username dari token yang dikirimkan.
        req.user = decoded.username; 
        req.decodedToken = decoded;

        // melanjutkan request ke controller menggunakan function next()
        next();
    } catch (error) {
        console.log(error);
        res.status(401).json({ error: 'Token is not valid'});
    }
};

module.exports = authMiddleware;
[/code]

Kemudian rubah function getProfile di controller jadi seperti ini.


[code language="plaintext"]
const getProfile = async (req, res) =&amp;amp;gt; {
    // mengambil data user yang di dapatkan dari file authMiddleware.js
    const username = req.user;
    res.json({ message: `Welcome to your profile ${username}!` });
}
[/code]

Setelah itu rubah code di file authRoutes.js


[code language="plaintext"]
// memuat express module.
const express = require('express');

// memuat authController agar function di dalamnya dapat di panggil di sini.
const authController = require('../controllers/authController');

// memuat authMiddleware
const authMiddleware = require('../middlewares/authMiddleware');

// memuat function Router dari express agar dapat mengunakan routing bawaan express.
const router = express.Router();

// membuat route dengan nama register dan mengarahkan ke function register yang berada di authController.js
// semisal ingin membuat data baru maka harus mengkakses http://localhost:3000/register denggan http method post.
router.post('/register', authController.register);
router.post('/login', authController.login);

// menambahkan middleware untuk mengamankan route profile
router.get('/profile', authMiddleware, authController.getProfile);

// mengexport router agar dapat di panggil oleh index.js
module.exports = router;
[/code]

Setelah itu pada terminal lakukan kombinasi tombol Ctrl + C dan tekan Enter hal itu bertujuan untuk mematikan service project dan kemudian jalankan perintah npm start untuk menjalankan service project. Kalau cara ini dianggap ribet bisa juga menggunakan nodemon jadi setiap menyimpan project maka otomatis service akan di restart.


Profile Request Dengan Middleware

Sekarang untuk melakukan request ke route profile harus menyertakan x-auth-token dan nilai tokennya, untuk nilai token bisa didapatkan saat proses login.


#9 Kesimpulan
Menggunakan Prisma sebagai ORM cukup menarik dan bagi yang ingin project ini bisa juga di ambil di sini.

						

Leave a Reply

Your email address will not be published. Required fields are marked *