Membuat Rest Api Dengan Fastify Dan Prisma
Berikut adalah contoh implementasi REST API sederhana menggunakan Fastify dan Prisma untuk website membership yang menjual tema WordPress. API ini mencakup fitur dasar seperti pengguna, produk (tema WordPress), dan transaksi pembelian.
1. Inisialisasi Proyek
Jalankan perintah berikut untuk mengatur proyek:
mkdir membership-api
cd membership-api
npm init -y
npm install fastify prisma @prisma/client fastify-plugin bcrypt
npx prisma init
2. Skema Prisma
Buka file prisma/schema.prisma
dan definisikan model berikut:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
password String
createdAt DateTime @default(now())
purchases Purchase[]
}
model Product {
id Int @id @default(autoincrement())
name String
description String
price Float
createdAt DateTime @default(now())
purchases Purchase[]
}
model Purchase {
id Int @id @default(autoincrement())
userId Int
productId Int
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
product Product @relation(fields: [productId], references: [id])
}
Berikut adalah langkah-langkah untuk membuat database di phpMyAdmin:
1. Buka phpMyAdmin
- Akses phpMyAdmin melalui browser Anda.
- Biasanya, alamatnya seperti:
http://localhost/phpmyadmin
atau alamat server Anda.
- Biasanya, alamatnya seperti:
- Masukkan username dan password MySQL Anda untuk login.
2. Buat Database Baru
- Setelah masuk, cari opsi “Databases” di menu atas dan klik.
- Di halaman “Databases”, Anda akan melihat form “Create database”.
- Isi nama database di kolom “Database name”.
- Pilih kolasi (collation) untuk database:
- Jika ragu, gunakan
utf8mb4_general_ci
. Ini adalah pilihan yang baik untuk mendukung karakter universal, termasuk emoji.
- Jika ragu, gunakan
- Klik tombol “Create”.
3. Verifikasi Database
- Setelah dibuat, Anda akan melihat nama database muncul di daftar database.
- Anda bisa mengeklik nama database tersebut untuk mulai menambahkan tabel atau memeriksa struktur.
4. Gunakan Database di Prisma
Pastikan nama database yang Anda buat sesuai dengan yang ditulis di file
.env
. Contoh:DATABASE_URL="mysql://username:password@localhost:3306/nama_database"
Jalankan migrasi Prisma untuk memastikan database siap digunakan:
npx prisma migrate dev --name init_mysql
Sekarang, database Anda sudah siap digunakan! 🎉
Setelah itu, jalankan migrasi untuk membuat database:
npx prisma migrate dev --name init
3. Membuat Server Fastify
Buat file server.js
untuk mendefinisikan server dan endpoint.
const fastify = require('fastify')({ logger: true });
const prisma = require('@prisma/client').PrismaClient;
const bcrypt = require('bcrypt');
const prismaClient = new prisma();
// Middleware untuk prisma
fastify.decorate('prisma', prismaClient);
// Rute Root
fastify.get('/', async (request, reply) => {
reply.send({ message: 'Selamat datang di Theme Store API!' });
});
// Registrasi User
fastify.post('/users/register', async (request, reply) => {
const { name, email, password } = request.body;
const hashedPassword = await bcrypt.hash(password, 10);
try {
const user = await prismaClient.user.create({
data: { name, email, password: hashedPassword },
});
reply.status(201).send(user);
} catch (error) {
reply.status(400).send({ error: 'Email sudah digunakan.' });
}
});
// Login User
fastify.post('/users/login', async (request, reply) => {
const { email, password } = request.body;
const user = await prismaClient.user.findUnique({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.password))) {
return reply.status(401).send({ error: 'Email atau password salah.' });
}
reply.send({ message: 'Login berhasil', user });
});
// Mendapatkan Semua Produk
fastify.get('/products', async (request, reply) => {
const products = await prismaClient.product.findMany();
reply.send(products);
});
// Menambahkan Produk Baru (Admin Only)
fastify.post('/products', async (request, reply) => {
const { name, description, price } = request.body;
const product = await prismaClient.product.create({
data: { name, description, price },
});
reply.status(201).send(product);
});
// Membeli Produk
fastify.post('/purchase', async (request, reply) => {
const { userId, productId } = request.body;
try {
const purchase = await prismaClient.purchase.create({
data: { userId, productId },
});
reply.status(201).send(purchase);
} catch (error) {
reply.status(400).send({ error: 'Gagal membeli produk.' });
}
});
// Menampilkan Transaksi User
fastify.get('/users/:userId/purchases', async (request, reply) => {
const { userId } = request.params;
const purchases = await prismaClient.purchase.findMany({
where: { userId: parseInt(userId) },
include: { product: true },
});
reply.send(purchases);
});
// Memulai Server
const start = async () => {
try {
await fastify.listen({ port: 3000 });
console.log('Server berjalan di http://localhost:3000');
} catch (error) {
fastify.log.error(error);
process.exit(1);
}
};
start();
Untuk menjalankan server Fastify yang sudah Anda buat, ikuti langkah-langkah berikut:
1. Pastikan Semua Dependensi Terinstal
Jalankan perintah berikut di terminal untuk memastikan semua dependensi sudah terpasang:
npm install
2. Jalankan Server
Gunakan salah satu metode berikut untuk menjalankan server:
a. Jalankan dengan Node.js
node <nama-file-server>.js
Gantilah <nama-file-server>
dengan nama file yang berisi kode server Anda, misalnya server.js
.
b. Jalankan dengan nodemon
(Opsional)
Nodemon adalah alat yang secara otomatis me-restart server setiap kali file berubah. Jika belum terpasang, instal terlebih dahulu:
npm install -g nodemon
Kemudian jalankan server:
nodemon <nama-file-server>.js
Untuk menjalankan server menggunakan nodemon melalui perintah yang didefinisikan di package.json
, ikuti langkah-langkah berikut:
1. Instal Nodemon
Jika nodemon belum terinstal secara lokal dalam proyek Anda, jalankan perintah berikut:
npm install --save-dev nodemon
2. Tambahkan Script di package.json
Buka file package.json
, lalu tambahkan script untuk menjalankan server menggunakan nodemon. Contohnya:
{
"scripts": {
"start": "node <nama-file-server>.js",
"dev": "nodemon <nama-file-server>.js"
}
}
Gantilah <nama-file-server>
dengan nama file server Anda, misalnya server.js
.
menjadi seperti ini
{
"name": "themestore",
"version": "1.0.0",
"description": "A Fastify-based membership website for selling WordPress themes.",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["membership", "wordpress-themes", "fastify", "prisma"],
"author": "Your Name",
"license": "ISC",
"dependencies": {
"@prisma/client": "^5.22.0",
"bcrypt": "^5.1.1",
"fastify": "^5.1.0",
"fastify-plugin": "^5.0.1",
"prisma": "^5.22.0"
},
"devDependencies": {
"nodemon": "^3.1.7"
}
}
3. Jalankan Server
Untuk menjalankan server, gunakan salah satu dari perintah berikut:
Jalankan Server dalam Mode Development (dengan nodemon):
npm run dev
Jalankan Server dalam Mode Production (dengan Node.js):
npm start
4. Hasil
Setelah menjalankan perintah npm run dev
, server Anda akan berjalan dengan nodemon. Setiap kali Anda menyimpan perubahan pada file, server akan otomatis me-restart.
Jika ada tambahan atau konfigurasi lain yang diperlukan, beri tahu saya! 😊
3. Akses Server
Setelah server berjalan, Anda akan melihat pesan di terminal seperti ini:
Server berjalan di http://localhost:3000
Anda dapat mengakses endpoint API menggunakan Postman, browser, atau alat lainnya.
4. Troubleshooting
Port Sudah Digunakan: Jika muncul pesan kesalahan seperti
EADDRINUSE: Address already in use
, ubah port server Anda di bagian ini:await fastify.listen({ port: 3000 });
Misalnya, ubah menjadi:
await fastify.listen({ port: 4000 });
Prisma Belum Dikonfigurasi: Pastikan file
schema.prisma
sudah benar, dan jalankan migrasi database dengan:npx prisma migrate dev
4. Testing API
Gunakan aplikasi seperti Postman untuk menguji endpoint berikut:
- POST
/users/register
- Registrasi pengguna baru. - POST
/users/login
- Login pengguna. - GET
/products
- Melihat semua produk. - POST
/products
- Menambahkan produk baru. - POST
/purchase
- Membeli produk. - GET
/users/:userId/purchases
- Melihat transaksi pengguna.
Berikut adalah contoh pengujian API menggunakan Postman atau alat serupa, serta skrip pengujian otomatis dengan Jest dan supertest:
1. Pengujian Manual dengan Postman
1.1. POST /users/register
Request:
{
"name": "John Doe",
"email": "[email protected]",
"password": "password123"
}
Response (201 Created):
{
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"password": "$2b$10$hashedPassword"
}
1.2. POST /users/login
Request:
{
"email": "[email protected]",
"password": "password123"
}
Response (200 OK):
{
"message": "Login berhasil",
"user": {
"id": 1,
"name": "John Doe",
"email": "[email protected]"
}
}
1.3. GET /products
Response (200 OK):
[
{
"id": 1,
"name": "WordPress Theme A",
"description": "A premium WordPress theme.",
"price": 59.99
}
]
1.4. POST /products
Request:
{
"name": "WordPress Theme B",
"description": "Another premium theme.",
"price": 49.99
}
Response (201 Created):
{
"id": 2,
"name": "WordPress Theme B",
"description": "Another premium theme.",
"price": 49.99
}
1.5. POST /purchase
Request:
{
"userId": 1,
"productId": 2
}
Response (201 Created):
{
"id": 1,
"userId": 1,
"productId": 2
}
1.6. GET /users/:userId/purchases
Request:
GET /users/1/purchases
Response (200 OK):
[
{
"id": 1,
"productId": 2,
"userId": 1,
"product": {
"id": 2,
"name": "WordPress Theme B",
"description": "Another premium theme.",
"price": 49.99
}
}
]
2. Pengujian Otomatis dengan Jest dan Supertest
Setup
Install dependensi:
npm install --save-dev jest supertest
Contoh Skrip Pengujian
File: tests/api.test.js
const request = require('supertest');
const fastify = require('../server'); // Sesuaikan path ke file server Anda
describe('API Testing', () => {
let userId;
it('Should register a new user', async () => {
const response = await request(fastify.server)
.post('/users/register')
.send({
name: 'John Doe',
email: '[email protected]',
password: 'password123',
});
expect(response.status).toBe(201);
expect(response.body.email).toBe('[email protected]');
userId = response.body.id; // Simpan userId untuk pengujian selanjutnya
});
it('Should login the user', async () => {
const response = await request(fastify.server)
.post('/users/login')
.send({
email: '[email protected]',
password: 'password123',
});
expect(response.status).toBe(200);
expect(response.body.message).toBe('Login berhasil');
});
it('Should get all products', async () => {
const response = await request(fastify.server).get('/products');
expect(response.status).toBe(200);
expect(Array.isArray(response.body)).toBeTruthy();
});
it('Should add a new product', async () => {
const response = await request(fastify.server)
.post('/products')
.send({
name: 'WordPress Theme C',
description: 'Another WordPress theme.',
price: 39.99,
});
expect(response.status).toBe(201);
expect(response.body.name).toBe('WordPress Theme C');
});
it('Should make a purchase', async () => {
const response = await request(fastify.server)
.post('/purchase')
.send({
userId: userId,
productId: 1,
});
expect(response.status).toBe(201);
expect(response.body.userId).toBe(userId);
});
it('Should get user purchases', async () => {
const response = await request(fastify.server).get(`/users/${userId}/purchases`);
expect(response.status).toBe(200);
expect(Array.isArray(response.body)).toBeTruthy();
});
});
Run Test
Jalankan pengujian dengan perintah:
npx jest
3. Hasil
- Postman cocok untuk pengujian manual.
- Jest dan Supertest memungkinkan pengujian otomatis end-to-end untuk memastikan semua endpoint bekerja sesuai harapan.
Jika ada tambahan, silakan beri tahu! 😊
Berikut adalah implementasi lanjutan dengan menambahkan autentikasi JWT, validasi input menggunakan Zod, dan role-based access control (RBAC).
1. Install Dependencies
Tambahkan dependensi yang diperlukan untuk pengembangan lanjutan:
npm install fastify-jwt zod fastify-auth
2. Konfigurasi Autentikasi JWT
Tambahkan plugin JWT di server.js
:
fastify.register(require('@fastify/jwt'), {
secret: 'your-secret-key', // Ganti dengan kunci rahasia yang aman
});
fastify.decorate('authenticate', async function (request, reply) {
try {
await request.jwtVerify();
} catch (err) {
reply.send(err);
}
});
fastify.decorate('authorizeAdmin', async function (request, reply) {
const user = request.user;
if (!user || user.role !== 'admin') {
return reply.status(403).send({ error: 'Akses ditolak. Hanya untuk admin.' });
}
});
3. Validasi Input dengan Zod
Tambahkan validasi menggunakan Zod di setiap endpoint:
const { z } = require('zod');
// Validasi untuk registrasi
const userSchema = z.object({
name: z.string().min(1, 'Nama tidak boleh kosong'),
email: z.string().email('Email tidak valid'),
password: z.string().min(6, 'Password minimal 6 karakter'),
});
// Validasi untuk produk
const productSchema = z.object({
name: z.string().min(1, 'Nama produk tidak boleh kosong'),
description: z.string().min(1, 'Deskripsi tidak boleh kosong'),
price: z.number().positive('Harga harus lebih dari 0'),
});
// Middleware validasi
function validate(schema) {
return (request, reply, done) => {
try {
schema.parse(request.body);
done();
} catch (err) {
reply.status(400).send(err.errors);
}
};
}
4. Tambahkan Role pada Model User
Tambahkan kolom role
di Prisma untuk membedakan pengguna biasa dan admin:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
password String
role String @default("user") // Bisa 'user' atau 'admin'
createdAt DateTime @default(now())
purchases Purchase[]
}
Jalankan migrasi untuk memperbarui database:
npx prisma migrate dev --name add_user_role
5. Endpoint dengan Role-Based Access Control (RBAC)
Registrasi dan Login
Tambahkan role
saat registrasi, dan kembalikan token saat login:
// Registrasi
fastify.post('/users/register', { preHandler: [validate(userSchema)] }, async (request, reply) => {
const { name, email, password, role = 'user' } = request.body;
const hashedPassword = await bcrypt.hash(password, 10);
try {
const user = await prismaClient.user.create({
data: { name, email, password: hashedPassword, role },
});
reply.status(201).send(user);
} catch (error) {
reply.status(400).send({ error: 'Email sudah digunakan.' });
}
});
// Login
fastify.post('/users/login', async (request, reply) => {
const { email, password } = request.body;
const user = await prismaClient.user.findUnique({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.password))) {
return reply.status(401).send({ error: 'Email atau password salah.' });
}
// Buat token JWT
const token = fastify.jwt.sign({ id: user.id, role: user.role });
reply.send({ message: 'Login berhasil', token });
});
Endpoint Produk dengan Proteksi
Lindungi endpoint sensitif menggunakan middleware autentikasi dan otorisasi:
// Mendapatkan Semua Produk (Public)
fastify.get('/products', async (request, reply) => {
const products = await prismaClient.product.findMany();
reply.send(products);
});
// Menambahkan Produk Baru (Admin Only)
fastify.post('/products', {
preHandler: [fastify.authenticate, fastify.authorizeAdmin, validate(productSchema)],
}, async (request, reply) => {
const { name, description, price } = request.body;
const product = await prismaClient.product.create({
data: { name, description, price },
});
reply.status(201).send(product);
});
Endpoint Transaksi
Lindungi transaksi hanya untuk pengguna yang login:
// Membeli Produk
fastify.post('/purchase', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const { productId } = request.body;
const userId = request.user.id;
try {
const purchase = await prismaClient.purchase.create({
data: { userId, productId },
});
reply.status(201).send(purchase);
} catch (error) {
reply.status(400).send({ error: 'Gagal membeli produk.' });
}
});
// Melihat Transaksi Pengguna
fastify.get('/users/me/purchases', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const userId = request.user.id;
const purchases = await prismaClient.purchase.findMany({
where: { userId },
include: { product: true },
});
reply.send(purchases);
});
6. Testing API
- POST
/users/register
Validasi input dan buat pengguna baru (tambahkanrole: 'admin'
jika perlu). - POST
/users/login
Dapatkan token JWT. - GET
/products
Endpoint publik. - POST
/products
Hanya admin yang bisa menambahkan produk. - POST
/purchase
Hanya pengguna login yang bisa membeli. - GET
/users/me/purchases
Hanya pengguna login yang bisa melihat transaksi mereka.
7. Pengembangan Selanjutnya
- Tambahkan penyegaran token (refresh token).
- Implementasikan pagination untuk daftar produk dan transaksi.
- Tambahkan fitur unggah file (misalnya, file tema WordPress).
Jika ada tambahan yang diinginkan, beri tahu saya!
Berikut adalah implementasi untuk pengembangan lebih lanjut seperti refresh token, pagination, dan unggah file (tema WordPress).
1. Refresh Token
Tambahkan mekanisme refresh token untuk memperbarui token yang sudah kedaluwarsa tanpa memaksa pengguna login kembali.
Modifikasi Model User
Tambahkan kolom refreshToken
:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
password String
role String @default("user")
refreshToken String? // Untuk menyimpan refresh token
createdAt DateTime @default(now())
purchases Purchase[]
}
Jalankan migrasi:
npx prisma migrate dev --name add_refresh_token
Endpoint Refresh Token
fastify.post('/token/refresh', async (request, reply) => {
const { refreshToken } = request.body;
if (!refreshToken) {
return reply.status(400).send({ error: 'Refresh token diperlukan.' });
}
const user = await prismaClient.user.findFirst({ where: { refreshToken } });
if (!user) {
return reply.status(401).send({ error: 'Token tidak valid.' });
}
const newAccessToken = fastify.jwt.sign({ id: user.id, role: user.role });
const newRefreshToken = fastify.jwt.sign({ id: user.id }, { expiresIn: '7d' });
await prismaClient.user.update({
where: { id: user.id },
data: { refreshToken: newRefreshToken },
});
reply.send({ accessToken: newAccessToken, refreshToken: newRefreshToken });
});
// Saat login, tambahkan refresh token
fastify.post('/users/login', async (request, reply) => {
const { email, password } = request.body;
const user = await prismaClient.user.findUnique({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.password))) {
return reply.status(401).send({ error: 'Email atau password salah.' });
}
const accessToken = fastify.jwt.sign({ id: user.id, role: user.role });
const refreshToken = fastify.jwt.sign({ id: user.id }, { expiresIn: '7d' });
await prismaClient.user.update({
where: { id: user.id },
data: { refreshToken },
});
reply.send({ accessToken, refreshToken });
});
2. Pagination
Tambahkan pagination untuk daftar produk dan transaksi menggunakan query parameter page
dan limit
.
Pagination Produk
fastify.get('/products', async (request, reply) => {
const { page = 1, limit = 10 } = request.query;
const products = await prismaClient.product.findMany({
skip: (page - 1) * limit,
take: parseInt(limit),
orderBy: { createdAt: 'desc' },
});
const total = await prismaClient.product.count();
reply.send({
data: products,
meta: {
total,
page: parseInt(page),
limit: parseInt(limit),
totalPages: Math.ceil(total / limit),
},
});
});
Pagination Transaksi
fastify.get('/users/me/purchases', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const userId = request.user.id;
const { page = 1, limit = 10 } = request.query;
const purchases = await prismaClient.purchase.findMany({
where: { userId },
skip: (page - 1) * limit,
take: parseInt(limit),
orderBy: { createdAt: 'desc' },
include: { product: true },
});
const total = await prismaClient.purchase.count({ where: { userId } });
reply.send({
data: purchases,
meta: {
total,
page: parseInt(page),
limit: parseInt(limit),
totalPages: Math.ceil(total / limit),
},
});
});
3. Unggah File (Tema WordPress)
Gunakan plugin fastify-multipart untuk menangani unggahan file.
Install Plugin
npm install fastify-multipart
Konfigurasi Endpoint Unggah File
Tambahkan endpoint untuk admin mengunggah tema WordPress:
const path = require('path');
const fs = require('fs');
// Konfigurasi fastify-multipart
fastify.register(require('@fastify/multipart'), {
limits: {
fileSize: 5 * 1024 * 1024, // Maksimal 5 MB
},
});
// Endpoint unggah file
fastify.post('/products/upload', {
preHandler: [fastify.authenticate, fastify.authorizeAdmin],
}, async (request, reply) => {
const data = await request.file();
const uploadDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
const filePath = path.join(uploadDir, data.filename);
const writeStream = fs.createWriteStream(filePath);
await data.file.pipe(writeStream);
reply.send({ message: 'File berhasil diunggah.', filePath });
});
4. Testing API
Refresh Token
- POST
/users/login
AmbilaccessToken
danrefreshToken
. - POST
/token/refresh
KirimrefreshToken
untuk mendapatkan token baru.
Pagination
- GET
/products?page=1&limit=5
Cek daftar produk dengan pagination. - GET
/users/me/purchases?page=1&limit=5
Cek daftar transaksi dengan pagination.
Unggah File
- POST
/products/upload
Kirim file tema WordPress dengan header Authorization: Bearer(hanya admin).
5. Pengembangan Selanjutnya
- Fitur Unduh File: Tambahkan endpoint untuk mengunduh file tema yang diunggah.
- Sistem Notifikasi: Kirim email notifikasi saat transaksi berhasil.
- Monitoring API: Tambahkan plugin seperti
fastify-metrics
untuk memantau performa API.
Jika ada tambahan lain, beri tahu saya! 😊
Berikut adalah pengembangan lebih lanjut untuk fitur unduh file, sistem notifikasi email, dan monitoring API:
1. Fitur Unduh File
Tambahkan endpoint untuk pengguna mengunduh tema WordPress yang telah dibeli.
Endpoint Unduh File
const path = require('path');
const fs = require('fs');
// Endpoint unduh file
fastify.get('/products/download/:productId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const { productId } = request.params;
const userId = request.user.id;
// Verifikasi apakah pengguna telah membeli produk
const purchase = await prismaClient.purchase.findFirst({
where: { userId, productId },
});
if (!purchase) {
return reply.status(403).send({ error: 'Anda belum membeli produk ini.' });
}
const product = await prismaClient.product.findUnique({ where: { id: parseInt(productId) } });
const filePath = path.join(__dirname, 'uploads', product.fileName); // Asumsi fileName disimpan di database
if (!fs.existsSync(filePath)) {
return reply.status(404).send({ error: 'File tidak ditemukan.' });
}
reply.header('Content-Disposition', `attachment; filename="${product.fileName}"`);
reply.send(fs.createReadStream(filePath));
});
2. Sistem Notifikasi Email
Gunakan Nodemailer untuk mengirim email notifikasi saat transaksi berhasil.
Install Nodemailer
npm install nodemailer
Konfigurasi Nodemailer
Tambahkan konfigurasi SMTP di file utama:
const nodemailer = require('nodemailer');
// Konfigurasi email
const transporter = nodemailer.createTransport({
host: 'smtp.example.com', // Ganti dengan penyedia SMTP Anda
port: 587,
secure: false,
auth: {
user: '[email protected]',
pass: 'your-email-password',
},
});
// Fungsi untuk mengirim email
async function sendEmail(to, subject, text) {
try {
await transporter.sendMail({
from: '"Your Company" <[email protected]>',
to,
subject,
text,
});
console.log('Email berhasil dikirim.');
} catch (error) {
console.error('Gagal mengirim email:', error);
}
}
Kirim Notifikasi Setelah Pembelian
Modifikasi endpoint pembelian untuk mengirim email:
fastify.post('/purchase', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const { productId } = request.body;
const userId = request.user.id;
try {
const purchase = await prismaClient.purchase.create({
data: { userId, productId },
});
const user = await prismaClient.user.findUnique({ where: { id: userId } });
const product = await prismaClient.product.findUnique({ where: { id: productId } });
// Kirim email notifikasi
await sendEmail(
user.email,
'Pembelian Berhasil',
`Halo ${user.name},\n\nAnda telah berhasil membeli tema "${product.name}".`
);
reply.status(201).send(purchase);
} catch (error) {
reply.status(400).send({ error: 'Gagal membeli produk.' });
}
});
3. Monitoring API
Gunakan plugin fastify-metrics untuk memantau performa API.
Install Plugin
npm install fastify-metrics
Konfigurasi Monitoring
Tambahkan plugin ke server utama:
const fastifyMetrics = require('fastify-metrics');
fastify.register(fastifyMetrics, {
endpoint: '/metrics', // Endpoint untuk monitoring
defaultMetrics: true,
});
// Endpoint untuk memeriksa kesehatan server
fastify.get('/health', async (request, reply) => {
reply.send({ status: 'ok' });
});
Monitoring dengan Prometheus atau Grafana
- Jalankan Prometheus dan Grafana di server Anda.
- Konfigurasikan Prometheus untuk menarik data dari endpoint
/metrics
. - Visualisasikan data di Grafana untuk memantau performa API.
4. Pengembangan Selanjutnya
- Dashboard Admin:
- Tambahkan fitur statistik penjualan, daftar pengguna, dan manajemen produk.
- Sistem Kupon Diskon:
- Tambahkan mekanisme kupon untuk memberikan diskon pada pembelian.
- Integrasi Payment Gateway:
- Gunakan API dari penyedia seperti Midtrans atau Stripe untuk menangani pembayaran.
- Otentikasi Dua Faktor (2FA):
- Tingkatkan keamanan dengan menambahkan fitur 2FA menggunakan aplikasi seperti Google Authenticator.
- CDN untuk File:
- Simpan file tema di penyedia CDN (misalnya, AWS S3) untuk mempercepat unduhan.
Jika ada tambahan lain atau detail tertentu yang ingin dikembangkan, beri tahu saya! 😊
Berikut adalah lanjutan pengembangan untuk fitur Dashboard Admin, Sistem Kupon Diskon, Integrasi Payment Gateway, Otentikasi Dua Faktor (2FA), dan CDN untuk File.
1. Dashboard Admin
Dashboard admin menyediakan data seperti total penjualan, produk terlaris, dan daftar pengguna.
Endpoint Statistik Penjualan
fastify.get('/admin/stats', {
preHandler: [fastify.authenticate, fastify.authorizeAdmin],
}, async (request, reply) => {
const totalSales = await prismaClient.purchase.count();
const totalRevenue = await prismaClient.purchase.aggregate({
_sum: { product: { price: true } },
});
const topProducts = await prismaClient.product.findMany({
orderBy: { purchases: { _count: 'desc' } },
take: 5,
include: { purchases: true },
});
reply.send({
totalSales,
totalRevenue: totalRevenue._sum.price || 0,
topProducts,
});
});
Endpoint Manajemen Produk
fastify.put('/admin/products/:id', {
preHandler: [fastify.authenticate, fastify.authorizeAdmin],
}, async (request, reply) => {
const { id } = request.params;
const { name, description, price } = request.body;
const updatedProduct = await prismaClient.product.update({
where: { id: parseInt(id) },
data: { name, description, price },
});
reply.send(updatedProduct);
});
2. Sistem Kupon Diskon
Tambahkan tabel kupon untuk menyimpan data kupon diskon.
Model Kupon
model Coupon {
id Int @id @default(autoincrement())
code String @unique
discount Float // Persentase diskon (0.0 - 1.0)
expiryDate DateTime
isActive Boolean @default(true)
}
Endpoint Validasi Kupon
fastify.post('/coupons/validate', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const { code } = request.body;
const coupon = await prismaClient.coupon.findUnique({ where: { code } });
if (!coupon || !coupon.isActive || new Date(coupon.expiryDate) < new Date()) {
return reply.status(400).send({ error: 'Kupon tidak valid atau telah kedaluwarsa.' });
}
reply.send({ discount: coupon.discount });
});
Integrasi Kupon ke Pembelian
Modifikasi endpoint pembelian:
fastify.post('/purchase', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const { productId, couponCode } = request.body;
let discount = 0;
if (couponCode) {
const coupon = await prismaClient.coupon.findUnique({ where: { code: couponCode } });
if (coupon && coupon.isActive && new Date(coupon.expiryDate) > new Date()) {
discount = coupon.discount;
}
}
const product = await prismaClient.product.findUnique({ where: { id: productId } });
const finalPrice = product.price * (1 - discount);
const purchase = await prismaClient.purchase.create({
data: { userId: request.user.id, productId, pricePaid: finalPrice },
});
reply.status(201).send(purchase);
});
3. Integrasi Payment Gateway
Integrasikan Midtrans untuk menangani pembayaran.
Install Midtrans SDK
npm install midtrans-client
Konfigurasi Payment Gateway
const midtransClient = require('midtrans-client');
const snap = new midtransClient.Snap({
isProduction: false,
serverKey: 'YOUR_SERVER_KEY',
clientKey: 'YOUR_CLIENT_KEY',
});
// Endpoint untuk membuat transaksi
fastify.post('/payment', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const { productId } = request.body;
const product = await prismaClient.product.findUnique({ where: { id: productId } });
const transaction = await snap.createTransaction({
transaction_details: {
order_id: `ORDER-${Date.now()}`,
gross_amount: product.price,
},
customer_details: {
email: request.user.email,
},
});
reply.send({ redirectUrl: transaction.redirect_url });
});
4. Otentikasi Dua Faktor (2FA)
Gunakan Google Authenticator untuk menambahkan keamanan 2FA.
Install Library 2FA
npm install speakeasy qrcode
Konfigurasi 2FA
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
// Endpoint untuk mengaktifkan 2FA
fastify.post('/2fa/setup', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const secret = speakeasy.generateSecret({ name: 'YourAppName' });
const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
await prismaClient.user.update({
where: { id: request.user.id },
data: { twoFactorSecret: secret.base32 },
});
reply.send({ qrCodeUrl, secret: secret.base32 });
});
// Endpoint untuk memverifikasi 2FA
fastify.post('/2fa/verify', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const { token } = request.body;
const user = await prismaClient.user.findUnique({ where: { id: request.user.id } });
const verified = speakeasy.totp.verify({
secret: user.twoFactorSecret,
encoding: 'base32',
token,
});
if (!verified) {
return reply.status(400).send({ error: 'Token tidak valid.' });
}
reply.send({ message: '2FA berhasil diverifikasi.' });
});
5. CDN untuk File
Simpan file di AWS S3 atau layanan serupa untuk meningkatkan kecepatan unduhan.
Install AWS SDK
npm install @aws-sdk/client-s3
Konfigurasi AWS S3
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3 = new S3Client({
region: 'YOUR_REGION',
credentials: {
accessKeyId: 'YOUR_ACCESS_KEY',
secretAccessKey: 'YOUR_SECRET_KEY',
},
});
// Unggah file ke S3
fastify.post('/products/upload', { preHandler: [fastify.authenticate, fastify.authorizeAdmin] }, async (request, reply) => {
const data = await request.file();
const buffer = await data.toBuffer();
const command = new PutObjectCommand({
Bucket: 'YOUR_BUCKET_NAME',
Key: data.filename,
Body: buffer,
ContentType: data.mimetype,
});
await s3.send(command);
reply.send({ message: 'File berhasil diunggah ke S3.', url: `https://YOUR_BUCKET_NAME.s3.amazonaws.com/${data.filename}` });
});