# Gold Loan Login API — Beginner's Guide

> Current application routes and setup are documented in [README.md](README.md). The lead API in section 18 below is a worked example; the implemented flow uses `/api/public/applications` and `/api/crm/applications` with one `applications` table. The server and Prisma commands read `.env.local`.

This is a **Node.js backend API**. It receives HTTP requests from a frontend (or Postman), checks the request, reads or writes MySQL data, and sends a JSON response back.

It has two login areas:

- **User login:** `POST /api/user/login`
- **CRM admin login:** `POST /api/crm/login`
- **Admin-only branch management:** `/api/crm/branches`

## 1. Technologies used

| Tool | Why this project uses it |
| --- | --- |
| Node.js | Runs JavaScript on the server. |
| Express | Defines HTTP routes and middleware. |
| MySQL | Permanently stores users, admins, and branches. |
| Prisma | Lets JavaScript code query MySQL with `prisma.user.findUnique(...)` instead of handwritten SQL. |
| bcryptjs | Hashes passwords and safely compares login passwords. |
| JSON Web Token (JWT) | Creates a short-lived access token after login. |
| dotenv | Loads private configuration from `.env` into `process.env`. |
| Helmet / CORS / Morgan | Security headers, cross-origin access rules, and development request logs. |
| Nodemon | Restarts the development server when files change. |

## 2. Folder structure

```text
goldloan/
├── server.js                 # Starts the application
├── package.json              # Dependencies and npm commands
├── prisma/
│   └── schema.prisma         # Database-table blueprint for Prisma
└── src/
    ├── app.js                # Creates Express app and global middleware
    ├── config/               # Environment and database setup
    ├── routes/               # URL + HTTP method mapping
    ├── controllers/          # HTTP request/response handling
    ├── services/             # Business rules
    ├── repositories/         # Database queries
    ├── models/               # Safe application representations of DB data
    ├── middlewares/          # Reusable processing before/after handlers
    └── utils/                # Small reusable helpers
```

The important design rule is separation of responsibilities:

```text
Client request
  → Route → Middleware → Controller → Service → Repository → Prisma → MySQL
                                                                  ↓
Client JSON response ← Controller ← Service ← Repository ← database result
```

This separation makes the code easier to test and change. For example, a controller should not contain raw database code, and a repository should not decide what HTTP status code to send.

## 3. Starting point: `server.js`

Node begins execution in `server.js`, because `package.json` sets it as the `main` file and both `npm start` and `npm run dev` run it.

```js
await initializeDatabase(); // connect Prisma to MySQL; optionally seed first admin
app.listen(env.port, ...);  // begin accepting HTTP requests
```

`await` means “wait until this asynchronous work finishes.” The `try/catch` ensures the API does not start if MySQL cannot be reached. `process.exit(1)` ends Node with an error status, useful for deployment systems.

## 4. `package.json` and commands

`package.json` describes the Node project.

| Item | Meaning |
| --- | --- |
| `"type": "module"` | Enables modern `import ... from ...` syntax. That is why every local import ends in `.js`. |
| `dependencies` | Packages needed when the API runs in production. |
| `devDependencies` | Development-only tools (`nodemon`, Prisma CLI). |
| `npm start` | Runs `node server.js`. |
| `npm run dev` | Runs Nodemon, which restarts on saved changes. |
| `npm run prisma:generate` | Generates Prisma Client from `prisma/schema.prisma`. |
| `npm run db:push` | Applies the Prisma schema to the configured database. |

`package-lock.json` records exact package versions. Keep it committed with `package.json`; do not normally edit it by hand.

## 5. Configuration: `src/config/env.js`

At the top, `import 'dotenv/config'` reads a `.env` file (if present) and puts values into `process.env`.

The file then creates one `env` object, so the rest of the program has a single consistent place for configuration:

```js
env.port                 // PORT or 4000
env.database.host        // DB_HOST or 127.0.0.1
env.jwt.accessSecret     // JWT_ACCESS_SECRET or development fallback
```

`||` means “use the value on the left, otherwise use the fallback on the right.” `Number(...)` changes text from environment variables into numbers.

Typical `.env` values (never commit this file or share real passwords):

```dotenv
PORT=4000
NODE_ENV=development
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_mysql_password
DB_NAME=gold_loan
JWT_ACCESS_SECRET=use_a_long_random_secret_here
JWT_ACCESS_EXPIRES_IN=15m
ADMIN_NAME=CRM Admin
ADMIN_USERNAME=admin
ADMIN_PASSWORD=choose_a_strong_password
ADMIN_BRANCH_ID=1
```

Important: `dev_access_secret` is only a convenient fallback. Set a long unique `JWT_ACCESS_SECRET` in every real environment. Anyone who knows the secret can forge tokens.

## 6. Database setup: `src/config/database.js`

This file does three jobs:

1. Builds `DATABASE_URL` from the separate DB settings if one was not already supplied. `encodeURIComponent` protects special characters in database usernames/passwords.
2. Dynamically imports and creates one shared `PrismaClient`, exported as `prisma`.
3. Connects to MySQL and optionally creates the first admin account.

`initializeDatabase()` calls `prisma.$connect()` and then `seedInitialAdmin()`.

### Initial admin seeding

If both `ADMIN_USERNAME` and `ADMIN_PASSWORD` are set, `seedInitialAdmin()`:

1. Ensures the configured branch ID exists (creates `Main Branch` if necessary) using `upsert`.
2. Searches for an admin with the same branch ID and username.
3. Creates one only when it does not already exist.
4. Stores a bcrypt hash, never the plaintext password.

That makes startup safe to repeat: it will not create duplicate seed admins.

### Important distinction

This code **connects to an existing database and seeds data**. It does not itself create/update tables from `schema.prisma`. Run these after setting `.env` and whenever the schema changes:

```bash
npm run prisma:generate
npm run db:push
```

The README sentence saying tables are created automatically at server start is therefore misleading for the code currently in this repository.

## 7. Database blueprint: `prisma/schema.prisma`

Prisma reads this file to generate its client and build/synchronize tables.

- `generator client` requests the JavaScript Prisma client.
- `datasource db` says the database is MySQL and gets its URL from `DATABASE_URL`.
- `model User`, `model Branch`, and `model Admin` describe three tables.

### Field syntax examples

| Syntax | Meaning |
| --- | --- |
| `id BigInt @id @default(autoincrement())` | Primary key, automatically incremented MySQL BIGINT. |
| `email String @unique` | Text field; duplicate emails are forbidden. |
| `String?` | Optional/nullable field. |
| `@default(now())` | Uses current time on creation. |
| `@updatedAt` | Prisma updates this timestamp whenever that record is updated. |
| `@map("password_hash")` | JavaScript uses `passwordHash`; MySQL column is `password_hash`. |
| `@@map("admins")` | JavaScript model `Admin` maps to the MySQL table `admins`. |

`Admin` belongs to one `Branch` through `branchId`. The relation and `@@unique([branchId, username])` mean the same username may exist in different branches, but cannot appear twice in the same branch.

Prisma returns database IDs as JavaScript `BigInt`; this project changes them to ordinary `Number`s in its model classes so JSON responses work. This is fine for normal IDs, but JavaScript numbers are only exact up to `9,007,199,254,740,991`.

## 8. Express application: `src/app.js`

`app.js` creates the Express app but does **not** start listening on a port. Keeping that separate from `server.js` is helpful for tests.

Middleware executes in top-to-bottom order:

1. `helmet()` adds common security-related HTTP headers.
2. `cors()` allows browsers from other origins to call the API. With no options, it is permissive; restrict allowed frontend origins before production.
3. `morgan('dev')` prints development request logs.
4. `express.json()` parses JSON request bodies into `req.body`.
5. `GET /health` returns a simple health-check response.
6. `app.use('/api', routes)` attaches feature routes under `/api`.
7. `notFoundHandler` catches unmatched routes.
8. `errorHandler` sends standardized errors.

Order matters: the error handlers must come after routes so they can catch errors and missing routes.

## 9. Routes: URL mapping

Routes decide which code runs for a URL and HTTP method. `src/routes/index.js` combines the two route groups:

```text
/api + /user + /login  = POST /api/user/login
/api + /crm  + /login  = POST /api/crm/login
```

`userRoutes.js` requires `email` and `password` before calling the user login controller.

`crmRoutes.js` first exposes public CRM login. The line `router.use(authenticateAdmin)` comes *after* login, so every branch route below it requires a valid admin bearer token.

| Method | Endpoint | Purpose | Authentication |
| --- | --- | --- | --- |
| GET | `/health` | Check API is running | No |
| POST | `/api/user/login` | Login a user by email/password | No |
| POST | `/api/crm/login` | Login an admin by branch ID/username/password | No |
| GET | `/api/crm/branches` | List branches | Admin token |
| GET | `/api/crm/branches/:id` | Read one branch | Admin token |
| POST | `/api/crm/branches` | Create a branch | Admin token |
| PUT | `/api/crm/branches/:id` | Update a branch | Admin token |
| DELETE | `/api/crm/branches/:id` | Delete a branch without admins | Admin token |

## 10. Controllers: HTTP in and out

Controllers are thin translators between Express and application logic.

- `UserLoginController.login` passes `req.body` to `userLoginService.login` and returns HTTP 200.
- `AdminLoginController.login` does the same for CRM admins.
- `BranchController` chooses appropriate HTTP statuses: 200 for reads/updates, 201 for creation, and 204 with no body for deletion.

They use `asyncHandler`, which ensures an error from an `async` function reaches Express’s central error handler instead of becoming an unhandled promise rejection.

## 11. Services: business rules

Services contain the decisions that define how the application behaves.

### `UserLoginService.js`

Finds a user by email, uses `bcrypt.compare(password, user.passwordHash)`, and returns safe user details plus a JWT. It gives the same “Invalid email or password” message for an unknown email and a wrong password, which avoids revealing whether an email is registered.

### `AdminLoginService.js`

Validates that `branch_id` is a positive integer, finds the admin by branch ID + username, validates its password, then returns admin details plus a JWT. The current error text says “Invalid email or password” even though CRM login uses `username`; that is only a wording issue, not a functional problem.

### `BranchService.js`

Validates branch IDs, turns absent records into 404 errors, and prevents a branch with existing admins from being deleted. That protects the branch/admin relationship.

## 12. Repositories and models: database boundary

Repositories are the only layer here that talks to Prisma:

- `UserRepository.findByEmail()`
- `AdminRepository.findByCredentials()`
- `BranchRepository` CRUD methods (`findAll`, `findById`, `create`, `update`, `delete`)

Each repository changes raw Prisma records into a model (`User`, `Admin`, or `Branch`). Models normalize `BigInt` IDs to `Number` IDs. `User.toSafeJSON()` and `Admin.toSafeJSON()` deliberately remove `passwordHash` so it cannot accidentally be sent to clients.

`BranchRepository.update()` and `delete()` use Prisma `updateMany`/`deleteMany`; these return a `count` instead of throwing when no row matches, allowing the service to return a clean 404 response.

## 13. Middleware and utilities

| File | Responsibility |
| --- | --- |
| `validateMiddleware.js` | `requireFields([...])` rejects missing, empty, `null`, or `undefined` body fields with HTTP 400. |
| `authMiddleware.js` | Reads `Authorization: Bearer <token>`, verifies the JWT, checks `accountType === 'admin'`, then sets `req.user`. |
| `errorMiddleware.js` | Turns known `ApiError`s into JSON responses; hides unexpected error details behind HTTP 500. |
| `ApiError.js` | A consistent custom error type with helpers for 400, 401, 403, 404, and 409 errors. |
| `asyncHandler.js` | Forwards rejected async controller promises to the error middleware. |
| `jwt.js` | Signs and verifies JWTs using values from `env.js`. |

## 14. Full example: creating a branch

1. An admin logs in at `POST /api/crm/login` and receives `accessToken`.
2. The client sends `POST /api/crm/branches`, a JSON body such as `{"name":"Pune Branch","address":"Pune"}`, and this header:

   ```http
   Authorization: Bearer <accessToken>
   ```

3. `authenticateAdmin` validates the token; `requireFields(['name'])` validates the body.
4. `BranchController.create` calls `BranchService.create`.
5. `BranchService` calls `BranchRepository.create`.
6. The repository uses `prisma.branch.create`, converts the DB row to a `Branch` object, and returns it.
7. The controller sends HTTP 201 JSON: `{ "success": true, "data": { ...branch } }`.

If any step throws an `ApiError`, the final `errorHandler` sends a consistent JSON error response such as:

```json
{
  "success": false,
  "message": "Branch not found"
}
```

## 15. First-time setup checklist

1. Install Node.js and make sure MySQL is running.
2. Run `npm install`.
3. Create `.env` using the configuration example above.
4. Create the MySQL database if your MySQL user cannot create it automatically: `CREATE DATABASE gold_loan;`.
5. Run `npm run prisma:generate`.
6. Run `npm run db:push`.
7. Run `npm run dev`.
8. Check `http://localhost:4000/health`.

The repository's README refers to `cp .env.example .env`, but no `.env.example` file currently exists. Use the example in this guide, or add such a file containing placeholders only (never secrets).

## 16. Vocabulary worth remembering

- **API endpoint:** A combination of HTTP method and URL, such as `POST /api/user/login`.
- **Request body:** JSON sent by the client, available as `req.body` after `express.json()` runs.
- **Middleware:** A function that can inspect/change a request before it reaches a controller.
- **HTTP status:** A number describing outcome: 200 success, 201 created, 204 success/no content, 400 invalid request, 401 unauthenticated, 403 unauthorized, 404 missing, 409 conflict, 500 server error.
- **Hash:** A one-way password representation. You compare a supplied password to a hash; you do not decrypt it.
- **JWT:** A signed token. The client presents it with later requests to prove it logged in.
- **ORM:** Object-relational mapper; Prisma is one. It maps code objects and queries to database tables.

## 17. Practical safety notes

- Add `.env` to `.gitignore`; never commit database passwords or JWT secrets.
- Do not return `passwordHash` in an API response. The current user/admin models already prevent this on login.
- Restrict CORS to your frontend domain in production.
- Use HTTPS in production because bearer tokens must not travel over unencrypted HTTP.
- Use a real migration workflow (typically `prisma migrate`) once this project has production data; `db push` is convenient during early development.

## 18. Worked example: create a customer lead API

Imagine a customer visits or calls and is interested in a gold loan. The frontend form collects their name, phone, email, address, gold quantity, and requested loan amount. We want an admin to save that information and later view the saved leads.

We will create these two endpoints:

| Method | Endpoint | Purpose |
| --- | --- | --- |
| POST | `/api/crm/leads` | Save a new lead from the frontend form. |
| GET | `/api/crm/leads` | Return all saved leads. |

These endpoints should be admin-only, so they belong in `crmRoutes.js` below `router.use(authenticateAdmin)`.

### The flow in plain language

```text
Frontend lead form
  → POST /api/crm/leads with JSON and admin access token
  → route checks token and required fields
  → controller receives the request
  → service validates business rules
  → repository saves the record through Prisma
  → MySQL stores it in the leads table
  → backend returns JSON for the frontend to display
```

The frontend does not need any backend source code. It only needs the API contract: URL, HTTP method, request body, required headers, and expected responses.

### Step 1 — Add the `Lead` database model

Add this at the bottom of `prisma/schema.prisma`:

```prisma
model Lead {
  id              BigInt   @id @default(autoincrement()) @db.UnsignedBigInt
  name            String   @db.VarChar(100)
  phone           String   @db.VarChar(20)
  email           String?  @db.VarChar(255)
  address         String?  @db.VarChar(255)
  goldAmount      Decimal  @map("gold_amount") @db.Decimal(10, 2)
  requestedAmount Decimal  @map("requested_amount") @db.Decimal(12, 2)
  createdAt       DateTime @default(now()) @map("created_at") @db.Timestamp(0)
  updatedAt       DateTime @updatedAt @map("updated_at") @db.Timestamp(0)

  @@map("leads")
}
```

This says: create a MySQL table named `leads`. The JavaScript/Prisma field name `goldAmount` maps to the MySQL column `gold_amount`; the same applies to `requestedAmount`.

After updating the schema, run:

```bash
npm run prisma:generate
npm run db:push
```

The first command updates the generated Prisma client. The second creates or updates the table in MySQL.

### Step 2 — Add a `Lead` model class

Create `src/models/Lead.js`:

```js
class Lead {
  constructor({
    id,
    name,
    phone,
    email,
    address,
    goldAmount,
    requestedAmount,
    createdAt,
    updatedAt,
  }) {
    this.id = Number(id);
    this.name = name;
    this.phone = phone;
    this.email = email;
    this.address = address;
    this.goldAmount = Number(goldAmount);
    this.requestedAmount = Number(requestedAmount);
    this.createdAt = createdAt;
    this.updatedAt = updatedAt;
  }
}

export default Lead;
```

This follows the existing `User`, `Admin`, and `Branch` models. In particular, it converts Prisma's `BigInt` ID into a normal number that can be included in JSON.

### Step 3 — Add the repository

Create `src/repositories/LeadRepository.js`:

```js
import Lead from '../models/Lead.js';
import { prisma } from '../config/database.js';

class LeadRepository {
  async create(data) {
    const record = await prisma.lead.create({
      data: {
        name: data.name,
        phone: data.phone,
        email: data.email || null,
        address: data.address || null,
        goldAmount: data.gold_amount,
        requestedAmount: data.requested_amount,
      },
    });

    return this._toModel(record);
  }

  async findAll() {
    const records = await prisma.lead.findMany({
      orderBy: { id: 'desc' },
    });

    return records.map((record) => this._toModel(record));
  }

  _toModel(row) {
    return new Lead(row);
  }
}

export default new LeadRepository();
```

This is the only new file that sends queries to the `leads` database table. `findMany` gets all records, and `orderBy: { id: 'desc' }` returns newest leads first.

### Step 4 — Add the service

Create `src/services/LeadService.js`:

```js
import leadRepository from '../repositories/LeadRepository.js';
import ApiError from '../utils/ApiError.js';

class LeadService {
  async create(data) {
    const goldAmount = Number(data.gold_amount);
    const requestedAmount = Number(data.requested_amount);

    if (!Number.isFinite(goldAmount) || goldAmount <= 0) {
      throw ApiError.badRequest('gold_amount must be greater than 0');
    }

    if (!Number.isFinite(requestedAmount) || requestedAmount <= 0) {
      throw ApiError.badRequest('requested_amount must be greater than 0');
    }

    return leadRepository.create({
      ...data,
      gold_amount: goldAmount,
      requested_amount: requestedAmount,
    });
  }

  async getAll() {
    return leadRepository.findAll();
  }
}

export default new LeadService();
```

The service is the right place for rules such as “an amount must be positive.” Later, rules such as duplicate phone prevention, maximum loan amount, and lead status belong here too.

### Step 5 — Add the controller

Create `src/controllers/LeadController.js`:

```js
import leadService from '../services/LeadService.js';
import asyncHandler from '../utils/asyncHandler.js';

class LeadController {
  create = asyncHandler(async (req, res) => {
    const lead = await leadService.create(req.body);

    res.status(201).json({
      success: true,
      data: lead,
    });
  });

  getAll = asyncHandler(async (req, res) => {
    const leads = await leadService.getAll();

    res.status(200).json({
      success: true,
      data: leads,
    });
  });
}

export default new LeadController();
```

The controller decides the HTTP response: `201 Created` after saving a lead and `200 OK` for the lead list.

### Step 6 — Register the routes

In `src/routes/crmRoutes.js`, add this import with the other imports:

```js
import leadController from '../controllers/LeadController.js';
```

Then add these lines **after** `router.use(authenticateAdmin);`:

```js
router.post(
  '/leads',
  requireFields(['name', 'phone', 'gold_amount', 'requested_amount']),
  leadController.create
);

router.get('/leads', leadController.getAll);
```

Because these lines are below `router.use(authenticateAdmin)`, both APIs require a CRM admin access token. Do not put them above it unless you intentionally want public access.

### Step 7 — API contract for the frontend

First, the CRM frontend logs in at `/api/crm/login` and receives an `accessToken`. When a user submits the lead form, the frontend calls this API:

```http
POST /api/crm/leads
Authorization: Bearer <admin_access_token>
Content-Type: application/json
```

Request body:

```json
{
  "name": "Rahul Sharma",
  "phone": "9876543210",
  "email": "rahul@example.com",
  "address": "Pune, Maharashtra",
  "gold_amount": 25.5,
  "requested_amount": 150000
}
```

Successful response (`201 Created`):

```json
{
  "success": true,
  "data": {
    "id": 1,
    "name": "Rahul Sharma",
    "phone": "9876543210",
    "email": "rahul@example.com",
    "address": "Pune, Maharashtra",
    "goldAmount": 25.5,
    "requestedAmount": 150000,
    "createdAt": "2026-09-19T00:00:00.000Z"
  }
}
```

If required fields are missing, the existing `requireFields` middleware returns HTTP `400`. If the token is absent or expired, `authenticateAdmin` returns HTTP `401`.

### Step 8 — Frontend example using `fetch`

The frontend gets `accessToken` from a successful CRM login and uses it when submitting the form:

```js
const response = await fetch('http://localhost:4000/api/crm/leads', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${accessToken}`,
  },
  body: JSON.stringify({
    name: form.name,
    phone: form.phone,
    email: form.email,
    address: form.address,
    gold_amount: Number(form.goldAmount),
    requested_amount: Number(form.requestedAmount),
  }),
});

const result = await response.json();

if (!response.ok) {
  throw new Error(result.message);
}

console.log('New lead saved:', result.data);
```

For the list page, call `GET /api/crm/leads` with the same `Authorization` header and show `result.data` in a table.

### Suggested improvements after the basic version works

- Validate Indian phone numbers and email format more strictly.
- Make phone unique if one lead per phone number is required.
- Add `status` such as `new`, `contacted`, `approved`, or `rejected`.
- Add a `branchId` or `createdByAdminId` field so every lead is connected to the responsible branch/admin.
- Add pagination to the list API instead of loading every lead once the data grows.
