DEV Community

Wildan Anugrah
Wildan Anugrah

Posted on

Membuat Simple Bank App Menggunakan Strapi v4

Hi, selamat datang di artikel kami. Untuk kali ini kita akan sharing dan belajar bareng terkait Strapi. Namun, sebelum kita mulai, ada beberapa hal yang mesti temen-temen pahami.

Pertama, Strapi adalah Headless Content Management System atau Headless CMS yang dimana sangat membantu untuk mempercepet waktu developer untuk membuat sebuah aplikasi.

Lalu apa itu Headless Content Management System atau Headless CMS? Headless CMS adalah sebuah CMS tanpa Frontend yang dimana content-nya dapat diakses melalui API. Sehingga dengan sebuah Headless CMS membantu temen-temen developer membuat backend aplikasi lebih cepat dan mudah.

API memungkinan dua aplikasi untuk saling berkomunikasi melalui sebuah protocol dengan message contract tertentu. Untuk pembahasan kali ini, kita akan menggunakan HTTP dan JSON

Strapi biasanya digunakan para developer untuk mengembangkan Static Websites, Mobile Apps, e-Commerce, Editorial, dan Corporate websites. Namun, tidak menutup kemungkinan untuk mengembangankan aplikasi lebih dari itu. Untuk di artikel kami kali ini, kami akan coba membuat aplikasi bank yang sederhana atau API Simple Bank App.

Kami merasa dengan mempelajari API Simple Bank App, dapat membantu kita untuk mempelajari sistem perbankan dalam scope kecil dan manfaat dari API terutama di era digital saat ini. Adapun yang akan kita pelajari di artikel ini antara lain:

  1. Business Requirements
  2. Design System
  3. Development

Business Requirements

API Simple Bank App melayani user dalam beberapa hal antara lain:

  1. User dapat mendaftarkan diri menjadi nasabah baru dan mendapatkan nomor Customer Identification File (CIF) dengan meng-input data antara lain:
    1. Nomor ID atau KTP
    2. Nama lengkap
  2. Nasabah dapat membuat nomor rekening baru dengan menginput data CIF
  3. Dapat melihat daftar nomor rekening dengan menginput data CIF
  4. Nasabah dapat men-topup uang ke nomor rekeningnya sendiri
    1. Nomor Rekening
    2. Amount
  5. Nasabah dapat menarik uang dari nomor rekeningnya sendiri dengan data antara lain:
    1. Nomor ID atau KTP
    2. Nomor Rekening
    3. Amount

Design system

Berdasarkan Business Requirement di sebelumnya, kami melihat bahwa ada beberapa service yang perlu kita bangun di dalam sistem kita antara lain:

  1. Customer service: service yang melayani nasabah dalam CRUD customer.

  2. Account service: service yang melayani nasabah dalam CRUD account.

  3. Transaction service: service yang melayani nasabah dalam CRUD transaction.

Development

  • Buka Terminal lalu jalankan perintah dibawah
npx create-strapi-app@latest app-simple-bank
Enter fullscreen mode Exit fullscreen mode
  • Masuk ke folder app-simple-bank dengan menjalankan code di bawah
cd app-simple-bank
Enter fullscreen mode Exit fullscreen mode
  • Buat dua file yaitu app.Dockerfile, docker-compose.yml, dan Makefile

  • Untuk app.Dockerfile, ketik kode di bawah ini

FROM node:16-alpine

# Installing libvips-dev for sharp Compatibility
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY ./package.json ./yarn.lock ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN yarn config set network-timeout 600000 -g && yarn install
WORKDIR /opt/app
COPY ./ .
RUN yarn build
EXPOSE 1337
CMD ["yarn", "develop"]
Enter fullscreen mode Exit fullscreen mode
  • Untuk compose-docker.yml. ketik kode di bawah ini
version: "3.7"
services:

  app-simple-bank:
    build:
      context: .
      dockerfile: ./app.Dockerfile
    container_name: app-simple-bank
    environment:
      - DATABASE_HOST=db-simple-bank
      - DATABASE_PORT=5432
      - DATABASE_NAME=postgres
      - DATABASE_USERNAME=postgres
      - DATABASE_PASSWORD=password
      - HOST=0.0.0.0
      - PORT=1337
      - APP_KEYS=mc68ZV26OzOPQ0A1ESUvNA==,b4sM7ksQE3eqfteYmqWzwA==,UUWwjcxNoDrGvSwnDDhwxA==,j7T57Bls1mSggOLIrsljkg==
      - API_TOKEN_SALT=nagThNX7aJINn6oaOpMOyg==
      - ADMIN_JWT_SECRET=9VrfUyP40Cqxy+6qNvucQQ==
      - TRANSFER_TOKEN_SALT=ar4K31sLK7NONBhHQ6t2zw==
    ports:
      - "8000:1337"
    depends_on:
      - db-simple-bank
    volumes:
      - ./src:/opt/app/src
      - ./config:/opt/app/config
      - ./package.json:/opt/app/package.json
      - ./public/uploads:/opt/app/public/uploads
    networks:
      app-net: {}

  db-simple-bank:
    image: postgres:alpine
    container_name: db-simple-bank
    environment:
      - POSTGRES_PASSWORD=password
    volumes:
      - ./data/pg-data:/var/lib/postgresql/data
    networks:
      app-net: {}

networks:
  app-net:
    external: true
    name: 'dev-to-network'
Enter fullscreen mode Exit fullscreen mode
  • Terakhir untuk Makefile, ketik kode di bawah ini
compose-up:
    docker compose up -d --build

compose-down:
    docker compose down
Enter fullscreen mode Exit fullscreen mode
  • Buka terminal di bawah, buat network baru di docker dengan menjalankan kode di bawah ini
docker network create dev-to-network
Enter fullscreen mode Exit fullscreen mode
  • Untuk menjalankan aplikasi nya, ketik kode di bawah ini di terminal
make compose-up
Enter fullscreen mode Exit fullscreen mode

Image description

Image description

  • Masukan data sesuai dengan yang diminta
  • Setelah masuk ke halaman dashboard admin, pilih menu Content-Type builder
  • Klik Create new collection type, lalu create Customer

Image description

  • Create beberapa field seperti dibawah

Image description

  • Klik Create new collection type, lalu create Account

Image description

  • Create beberapa field seperti dibawah

Image description

  • Klik Create new collection type, lalu create Transaction

Image description

  • Create beberapa field seperti dibawah

Image description

  • Create lifecycles.js file di src/api/content-types/customer/lifecycles.js, lalu ketikan kode di bawah ini
// src/api/content-types/customer/lifecycles.js

module.exports = {
    beforeCreate: async (event) => {

        const generate_cif = async (length) => {
            let result = '';
            const characters = '0123456789';
            const charactersLength = characters.length;
            let counter = 0;
            while (counter < length) {
                result += characters.charAt(Math.floor(Math.random() * charactersLength));
                counter += 1;
            }
            return result;
        }

        const get_cif = async () => {

            let cif = ""
            let customer = []
            do {
                cif = await generate_cif(10)
                customer = await strapi.entityService.findMany("api::customer.customer", {
                    filters: {
                        cif: cif
                    }
                })
                console.log(`customer: ${customer}`)
                console.log(`cif: ${cif}`)
            } while (customer.length != 0)

            return cif
        }

        const { data } = event.params;
        data.cif = await get_cif()
    },
};
Enter fullscreen mode Exit fullscreen mode
  • Create lifecycles.js file di src/api/content-types/account/lifecycles.js, lalu ketikan kode di bawah ini
// src/api/content-types/account/lifecycles.js

const utils = require('@strapi/utils');
const { ApplicationError } = utils.errors;

module.exports = {
    beforeCreate: async (event) => {

        const generate_account_number = async (length) => {
            let result = '';
            const characters = '0123456789';
            const charactersLength = characters.length;
            let counter = 0;
            while (counter < length) {
                result += characters.charAt(Math.floor(Math.random() * charactersLength));
                counter += 1;
            }
            return result;
        }

        const get_account_number = async () => {

            let account_number = ""
            let account = []
            do {
                account_number = await generate_account_number(10)
                account = await strapi.entityService.findMany("api::account.account", {
                    filters: {
                        number: account_number
                    }
                })
                console.log(`account: ${account}`)
                console.log(`account_number: ${account_number}`)
            } while (account.length != 0)

            return account_number
        }

        const { data } = event.params;

        const customer = await strapi.entityService.findMany("api::customer.customer", {
            filters: {
                cif: data.cif_number
            }
        })

        if(customer.length == 0)
        {
            throw new ApplicationError('Invalid cif number', { message: `can not find '${data.cif_number}' cif number` }); 
        }
        else
        {
            data.number = await get_account_number()
            data.balance = 0
        }
    },
};
Enter fullscreen mode Exit fullscreen mode
  • Create file custom-transaction.js di src/api/transaction/controllers/custom-transaction.js, lalu ketikan kode di bawah ini
// src/api/transaction/controllers/custom-transaction.js

const utils = require('@strapi/utils');
const { ApplicationError } = utils.errors;

const get_account = async (account_number) => {
    const accountResponse = await strapi.entityService.findMany("api::account.account",
        {
            filters: {
                number: account_number
            }
        }
    )

    return accountResponse[0]
}

const generate_journal = async (length) => {
    let result = '';
    const characters = '0123456789';
    const charactersLength = characters.length;
    let counter = 0;
    while (counter < length) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
        counter += 1;
    }
    return result;
}

const get_journal = async () => {

    let journal = ""
    let transaction = []
    do {
        journal = await generate_journal(10)
        transaction = await strapi.entityService.findMany("api::transaction.transaction", {
            filters: {
                journal: journal
            }
        })
        console.log(`journal: ${journal}`)
    } while (transaction.length != 0)

    return journal
}

module.exports = {
    async debit(ctx, next) {

        const requestBody = ctx.request.body.data
        const account = await get_account(requestBody.account_number)

        if (account.length == 0) {
            throw new ApplicationError('Invalid account number', { message: `Can not find ${requestBody.account_number} account number` });
        }
        else {
            const new_balance = account.balance - requestBody.amount
            const transaction = {
                data: {
                    journal: await get_journal(),
                    account_number: requestBody.account_number,
                    type: requestBody.type,
                    status: "SUCCEED",
                    action: "DEBIT",
                    amount: requestBody.amount,
                    publishedAt: new Date()
                }
            }

            await strapi.entityService.create("api::transaction.transaction", transaction)

            await strapi.entityService.update("api::account.account", account.id, {
                data: {
                    balance: new_balance
                }
            })

            return transaction
        }

    },
    async credit(ctx, next) {

        const requestBody = ctx.request.body.data

        const account = await get_account(requestBody.account_number)
        console.log(account)

        if (account.length == 0) {
            throw new ApplicationError('Invalid account number', { message: `Can not find ${requestBody.account_number} account number` });
        }
        else {
            const new_balance = account.balance + requestBody.amount
            const transaction = {
                data: {
                    journal: await get_journal(),
                    account_number: requestBody.account_number,
                    type: requestBody.type,
                    status: "SUCCEED",
                    action: "CREDIT",
                    amount: requestBody.amount,
                    publishedAt: new Date()
                }
            }

            await strapi.entityService.create("api::transaction.transaction", transaction)

            await strapi.entityService.update("api::account.account", account.id, {
                data: {
                    balance: new_balance
                }
            })

            return transaction

        }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Ubah nama file transaction.js menjadi 01-transaction.js di folder src/api/transaction/routes/

  • Create file 02-transactions.js di folder src/api/transaction/routes/, lalu ketikan kode di bawah ini

module.exports = {
    routes: [
        { // Path defined with an URL parameter
            method: 'POST',
            path: '/transactions/debit',
            handler: 'custom-transaction.debit',
        },
        { // Path defined with an URL parameter
            method: 'POST',
            path: '/transactions/credit',
            handler: 'custom-transaction.credit',
        },
    ]
}
Enter fullscreen mode Exit fullscreen mode
  • Re-run kembali aplikasi dengan menjalankan command ini di terminal
make compose-down; make compose-up
Enter fullscreen mode Exit fullscreen mode

Image description

  • Buka Permissions untuk Account dan Select All semua
  • Buka Permissions untuk Customer dan Select All semua
  • Buka Permissions untuk Transaction dan Select All semua
  • Lalu masuk ke menu Content-Manager > User lalu klik Create new entry

Image description

  • Input data sesuai form dan pastikan untuk mengingat username, email, dan password. Jangan lupa pilih confirmed-nya true dan role-nya Authenticated

Image description

  • Pengembangan aplikasi sudah selesai, teman-teman bisa menjalankan test dengan Postman atau semacamnya. Disini kami menggunakan Rest Client VSCode extension dengan create file normal.http dan menjalankan kode di bawah ini
@host=http://localhost:8000

POST {{host}}/api/auth/local
Content-Type: application/json

{
    "identifier" : "developer",
    "password" : "password"
}

### create customer

POST {{host}}/api/customers
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjgwNDE1MzI1LCJleHAiOjE2ODMwMDczMjV9.HaZpvrouMAjkvm51w0DkRwXjKOdVrInkYvcKfUE_teY

{
    "data" : {
        "id_number" : "3175023005912345",
        "fullname" : "Wildan Anugrah"
    }
}

### create account
POST {{host}}/api/accounts
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjgwNDE1MzI1LCJleHAiOjE2ODMwMDczMjV9.HaZpvrouMAjkvm51w0DkRwXjKOdVrInkYvcKfUE_teY

{
    "data" : {
        "cif_number" : "5685265072",
        "type" : "DEPOSIT"
    }
}

### accounts
GET {{host}}/api/accounts
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjgwNDE1MzI1LCJleHAiOjE2ODMwMDczMjV9.HaZpvrouMAjkvm51w0DkRwXjKOdVrInkYvcKfUE_teY


### credit
POST {{host}}/api/transactions/credit
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjgwNDE1MzI1LCJleHAiOjE2ODMwMDczMjV9.HaZpvrouMAjkvm51w0DkRwXjKOdVrInkYvcKfUE_teY


{
    "data" : {
        "account_number" : "1473367796",
        "payment_type" : "CREDIT",
        "amount" : 10000
    }
}

### debit
POST {{host}}/api/transactions/debit
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjgwNDE1MzI1LCJleHAiOjE2ODMwMDczMjV9.HaZpvrouMAjkvm51w0DkRwXjKOdVrInkYvcKfUE_teY

{
    "data" : {
        "account_number" : "1473367796",
        "payment_type" : "DEBIT",
        "amount" : 2000
    }
}
Enter fullscreen mode Exit fullscreen mode

Cheers :)

Github: https://github.com/wildananugrah/simple-bank-with-strapi-v4

Top comments (0)