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

  1. Akses phpMyAdmin melalui browser Anda.
    • Biasanya, alamatnya seperti:
      http://localhost/phpmyadmin
      atau alamat server Anda.
  2. Masukkan username dan password MySQL Anda untuk login.

2. Buat Database Baru

  1. Setelah masuk, cari opsi “Databases” di menu atas dan klik.
  2. Di halaman “Databases”, Anda akan melihat form “Create database”.
  3. Isi nama database di kolom “Database name”.
  4. Pilih kolasi (collation) untuk database:
    • Jika ragu, gunakan utf8mb4_general_ci. Ini adalah pilihan yang baik untuk mendukung karakter universal, termasuk emoji.
  5. Klik tombol “Create”.

3. Verifikasi Database

  1. Setelah dibuat, Anda akan melihat nama database muncul di daftar database.
  2. Anda bisa mengeklik nama database tersebut untuk mulai menambahkan tabel atau memeriksa struktur.

4. Gunakan Database di Prisma

  1. Pastikan nama database yang Anda buat sesuai dengan yang ditulis di file .env. Contoh:

    DATABASE_URL="mysql://username:password@localhost:3306/nama_database"
    
  2. 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:

  1. POST /users/register - Registrasi pengguna baru.
  2. POST /users/login - Login pengguna.
  3. GET /products - Melihat semua produk.
  4. POST /products - Menambahkan produk baru.
  5. POST /purchase - Membeli produk.
  6. 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

  1. POST /users/register
    Validasi input dan buat pengguna baru (tambahkan role: 'admin' jika perlu).
  2. POST /users/login
    Dapatkan token JWT.
  3. GET /products
    Endpoint publik.
  4. POST /products
    Hanya admin yang bisa menambahkan produk.
  5. POST /purchase
    Hanya pengguna login yang bisa membeli.
  6. 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

  1. POST /users/login
    Ambil accessToken dan refreshToken.
  2. POST /token/refresh
    Kirim refreshToken untuk mendapatkan token baru.

Pagination

  1. GET /products?page=1&limit=5
    Cek daftar produk dengan pagination.
  2. GET /users/me/purchases?page=1&limit=5
    Cek daftar transaksi dengan pagination.

Unggah File

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

  1. Dashboard Admin:
    • Tambahkan fitur statistik penjualan, daftar pengguna, dan manajemen produk.
  2. Sistem Kupon Diskon:
    • Tambahkan mekanisme kupon untuk memberikan diskon pada pembelian.
  3. Integrasi Payment Gateway:
    • Gunakan API dari penyedia seperti Midtrans atau Stripe untuk menangani pembayaran.
  4. Otentikasi Dua Faktor (2FA):
    • Tingkatkan keamanan dengan menambahkan fitur 2FA menggunakan aplikasi seperti Google Authenticator.
  5. 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}` });
});

You May Also Like

apa-itu-vps.jpg
Digital ocean

Svelte 5

VPS adalah sebuah server yang dibagi berdasarkan Virtual Machines yang bisa di install OS sendiri. Lalu benarkah VPS bisa meningkatkan kinerja website anda?

cara-membuat-droplet.jpg
Digital ocean

Cara Membuat Droplets Digital Ocean dengan Benar

Droplets merupakan istilah untuk menyebut sebuah VPS pada Digital Ocean. Berikut ini panduan lengkap cara membuat droplets Digital Ocean gratis 2 bulan.

apa-itu-vps.jpg
Digital ocean

Apa itu VPS? Benarkah VPS Bisa Meningkatkan Kinerja Website Anda?

VPS adalah sebuah server yang dibagi berdasarkan Virtual Machines yang bisa di install OS sendiri. Lalu benarkah VPS bisa meningkatkan kinerja website anda?