This project is a backend API for managing user accounts and transactions for a lending platform. It includes features such as account creation, funding, withdrawal, funds transfer to another user’s account, and transaction history. The project is built using Node.js, TypeScript, MySQL, and Knex.js for database management.
- Node.js
- MySQL
- Knex.js
- Clone the repository:
git clone https://github.com/acedeywin/demo-credit-api.git
npm install
Create a .env file with the following variables:
DB_HOST=your-database-host
DB_USER=your-database-user
DB_PASSWORD=your-database-password
DB_NAME=your-database-name
DB_PORT=your-database-port
PORT=your-server-port
ADJUTOR_API_KEY=your-adjutor-api-key
ADJUTOR_URL=https://adjutor.lendsqr.com/v2/verification
REDIS_URL=your-redis-caching-url
USER_EMAIL=your-email-address (gmail preferable)
EMAIL_PASSWORD=your-email-app-password (see this youtube tutorial to set it setup: https://www.youtube.com/watch?v=cqdAS49RthQ&t=97s)
EMAIL_HOST=your-emai-host (e.g: smtp.gmail.com)
EMAIL_PORT=your-email-port
EMAIL_SECURE=false
EMAIL_SENDER=your-sender-title
EMAIL_SERVICE=your-email-service (e.g: gmail)
JWT_SECRET=your-jwt-secret
JWT_EXPIRY=your-jwt-expiry-interval (e.g: 1h)
npm run compile npm run start
├── src
│ ├── config # Configuration files (e.g., database config)
│ ├── controllers # API controllers to handle HTTP requests
│ ├── models # Database models (e.g., Account, User)
│ ├── services # Business logic for accounts, transactions, etc.
│ ├── routes # Route definitions for the API endpoints
│ ├── middleware # Middleware functions (e.g., authentication, validation)
│ ├── utils # Helper functions (e.g., validation, error handling)
│ ├── types # Custom TypeScript types and interfaces
│ ├── tests # Unit and integration tests
│ ├── app.ts # App entry point (express instance setup)
│ └── index.ts # Main server file (starts the server)
├── .env # Environment variables (keep sensitive data here)
├── .gitignore # Specifies files ignored by Git
├── package.json # Node dependencies and scripts
├── tsconfig.json # TypeScript configuration file
└── README.md # Documentation for the project
The project follows a modular MVC structure. The main components are:
- Controllers: Handle HTTP requests and responses (located in
src/controllers
). - Services: Business logic is encapsulated in services for reusability and separation of concerns (located in
src/services
). - Models: Database models are managed with Knex for query building and MySQL integration (located in
src/models
). - Routes: All API routes are defined and mapped to the controllers (located in
src/routes
).
- Allows users to create an account.
- Verify user's NIN before creation of an account
- Checks the user's blacklist status using the Karma API before account creation.
- Generates a unique account number and initializes the account with a zero balance.
- Sends verification email to user's email address to verify user's authenticity
- Enables users to deposit funds into their account.
- Enables users to withdraw funds from their account.
- Enables users to transfer to another user’s account.
- Updates the account balance and logs each transaction in the transaction history.
- Retrieve the transaction history for an account.
- Each transaction includes details such as amount, type (credit or debit), and balance after the transaction.
- Method: POST
- Endpoint:
/api/v1/user/register
- Request Body:
{ "first_name": "Tom", "last_name": "Dede", "email": "user@gmail.com", "password": "Awe@#254redtgf5", "confirm_password": "Awe@#254redtgf5", "phone_number": "08543409211", "dob": "12-09-1997", "nin": "36900362314", "initial_deposit": "4500" //optional }
- Response:
{
"status": "success",
"message": "Account successfully created. Verification code sent to user@gmail.com",
"data": {
"user": {
"first_name": "Tom",
"last_name": "Dede",
"email": "user@gmail.com",
"phone_number": "08543409211",
"email_verified": false,
"nin_verified": "verified"
},
"account": {
"account_number": "5112293715",
"balance": "4500.00"
}
}
}
- Method: PUT
- Endpoint:
/api/v1/user/verify-user
- Request Body:
{ "email": "user@gmail.com", "code": "109722" }
- Response:
{
"status": "success",
"message": "Account successfully verified. Proceed to login."
}
-
Method: GET
-
Endpoint:
/api/v1/user?user_id=209cf65bxxxx
-
Authorization: Bearer
<token>
-
Response:
{
"status": "success",
"message": "'User account fetched successfully.",
"message": "User account fetched successfully.",
"data": {
"user": {
"id": "209cf65b-df32-42c6-b8e4-e80f02886753",
"first_name": "Tom",
"last_name": "Hardy",
"dob": "12-09-1997",
"email": "user@gmail.com",
"phone_number": "08054356461",
"email_verified": 1,
"nin_verified": "verified",
"created_at": "2024-11-02T22:14:58.000Z",
"updated_at": "2024-11-02T22:14:58.000Z"
},
"account": [
{
"id": "3d7ca163-9a83-11ef-b920-0653f87c7c59",
"user_id": "209cf65b-df32-42c6-b8e4-e80f02886753",
"account_number": "2780799928",
"balance": "66000.00",
"status": "active",
"created_at": "2024-11-04T08:03:13.000Z",
"updated_at": "2024-11-04T08:03:13.000Z"
},
{
"id": "e5ae1514-9967-11ef-b920-0653f87c7c59",
"user_id": "209cf65b-df32-42c6-b8e4-e80f02886753",
"account_number": "7704292343",
"balance": "223300.56",
"status": "active",
"created_at": "2024-11-02T22:14:58.000Z",
"updated_at": "2024-11-02T22:14:58.000Z"
}
]
}
}
- Method: POST
- Endpoint:
/api/v1/auth/#
- Request Body:
{ "email": "user@gmail.com", "password": "Awe@#254redtgf5" }
- Response:
{
"status": "success",
"message": "Logged in successfully",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCxxxxxxxxxxxxx4iHxTbQPC01I",
"data": {
"user": {
"id": "209cf65b-df32-42c6-b8e4-e80f02886753",
"first_name": "Tom",
"last_name": "Dede",
"dob": "12-09-1997",
"email": "user@gmail.com",
"phone_number": "08543409211",
"email_verified": 1,
"nin_verified": "verified",
"created_at": "2024-11-02T21:14:58.000Z",
"updated_at": "2024-11-02T21:14:58.000Z"
}
}
}
- Method: POST
- Endpoint:
/api/v1/auth/logout
- Response:
{
"status": "success",
"message": "'Logged out successfully."
}
- Method: POST
- Endpoint:
/api/v1/auth/reset-password
- Request Body:
{ "email": "user@gmail.com" }
- Response:
{
"status": "success",
"message": "A password reset code will been sent to user@gmail.com if it exist."
}
- Method: PUT
- Endpoint:
/api/v1/auth/change-password
- Request Body:
{ "email": "user@gmail.com", "password": "De#$r560fkdewWQ", "confirm_password": "De#$r560fkdewWQ", "code": "519735" }
- Response:
{
"status": "success",
"message": "Password reset was successful."
}
-
Method: POST
-
Endpoint:
/api/v1/account/create-new-account?user_id=209cf65bxxx
-
Authorization: Bearer
<token>
-
Response:
{
"status": "success",
"message": "User account created successfully",
"data": { "account_number": "2780799928" }
}
- Method: POST
- Endpoint:
/api/v1/transaction/fund-account?user_id=209cf65bxxxx
- Authorization: Bearer
<token>
- Request Body:
{ "amount": "45000", "account_number": "2780799928", "description": "Loan funding" //optional }
- Response:
{
"status": "success",
"message": "Account funding successful."
}
- Method: POST
- Endpoint:
/api/v1/transaction/withdraw-fund?user_id=209cf65bxxxx
- Authorization: Bearer
<token>
- Request Body:
{ "amount": "45000", "account_number": "2780799928", "description": "Loan funding" //optional }
- Response:
{
"status": "success",
"message": "Fund withdrawal successful."
}
- Method: POST
- Endpoint:
/api/v1/transaction/transfer-fund?user_id=209cf65bxxxx
- Authorization: Bearer
<token>
- Request Body:
{ "amount": "45000", "sender_account": "2780799928", "receiver_account": "7704292343", "description": "Loan funding" //optional }
- Response:
{
"status": "success",
"message": "Fund transfer successful."
}
-
Method: GET
-
Endpoint:
/api/v1/transaction/history?user_id=209cf65bxxxx&account_number=2780799921&page=1&size=10
-
Authorization: Bearer
<token>
-
Response:
{
"status": "success",
"message": "'Transactions fetched successfully.",
"data": {
"account": {
"id": "3d7cxxxx-9xxxxxx",
"user_id": "209cxxxx-df32-xxxxx",
"account_number": "2780799928",
"balance": "11000.00",
"status": "active",
"created_at": "2024-11-04T08:03:13.000Z",
"updated_at": "2024-11-04T08:03:13.000Z"
},
"transactions": {
"transactions": [
{
"id": "df3d4bb8-9bfd-xxxxxxx",
"account_id": "052c8878-9xxxxx",
"amount": "21500.00",
"balance_after": "43500.00",
"transaction_type": "credit",
"description": "Loan funding",
"reference_id": "SOC-3959629329",
"created_at": "2024-11-06T04:13:34.000Z",
"updated_at": "2024-11-06T04:13:34.000Z"
},
{
"id": "7ed22f13-9a83-xxxx",
"account_id": "3d7ca163-9xxxx",
"amount": "17000.00",
"balance_after": "28000.00",
"transaction_type": "debit",
"description": "Loan funding",
"reference_id": "SOD-2036499709",
"created_at": "2024-11-04T08:05:03.000Z",
"updated_at": "2024-11-04T08:05:03.000Z"
}
],
"totalPages": 6,
"currentPage": 1,
"page": 1
}
}
}
- Database Schema: Separate tables for users, accounts, and transactions for better data organization and scalability.
- Knex for Query Building: Chose Knex for its flexibility and compatibility with MySQL.
- Modular Service Layer: Services were created to handle business logic separately from controllers, enhancing testability and readability.
The following E-R diagram illustrates the database structure for this project, showing relationships between the Users
, Accounts
, and Transactions
tables.
The database de#cludes the following entities:
- Users: Stores user profile information.
- Accounts: Linked to each user, holding account-specific information like account_number and balance.
- Transactions: Logs each transaction for an account, including the type (credit or debit) and resulting balance.
Unit tests were written for core services, middlewares, controllers, and utils functions. Jest was used as the test framework.
To run tests:
npm run test
- NIN Verification: Currently, users' NINs are not saved in the database after verification. So, the only way to ensure a user doesn't have a double profile account is to check against phone numbers in the database, which are mapped to each NIN. A better way would be to save users' NINs and use that to screen account creations.
- Improved Error Handling: More detailed error responses could be implemented for finer-grained error handling.