A secure REST API for a simple CRM focused on lead management. Counselors can register/login, view public (unclaimed) enquiries, claim leads to make them private, and view their own claimed enquiries. Public users can submit enquiries without authentication.
- Node.js, Express.js
- SQLite (via Sequelize ORM)
- JWT (jsonwebtoken)
- bcrypt for password hashing
- dotenv, nodemon
- Employees
- POST
/api/employees/register– register a counselor - POST
/api/employees/login– login and receive JWT
- POST
- Enquiries
- POST
/api/enquiries/public– submit enquiry (public, no auth) - GET
/api/enquiries/public– list unclaimed enquiries (auth) - PATCH
/api/enquiries/:id/claim– claim an unclaimed enquiry (auth) - GET
/api/enquiries/private– list enquiries claimed by logged-in counselor (auth)
- POST
crm-backend/
├── config/
│ └── database.js
├── controllers/
│ ├── employeeController.js
│ └── enquiryController.js
├── middlewares/
│ └── auth.js
├── models/
│ ├── employee.js
│ ├── enquiry.js
│ └── index.js
├── routes/
│ ├── employeeRoutes.js
│ └── enquiryRoutes.js
├── server.js
├── package.json
└── README.md
- Prerequisites
- Install Node.js 18+
- Install dependencies
npm install
- Environment variables
- Create a
.envfile in the project root:
PORT=3000
JWT_SECRET=CHANGE_ME_SUPER_SECRET
DB_DIALECT=sqlite
DB_STORAGE=./crm_db.sqlite
- Run the server
- Development (auto-reload):
npm run dev
- Or normal start:
npm start
- You should see:
Server listening on port 3000
Importantly, for all authenticated requests, add a header:
- Key:
Authorization - Value:
Bearer YOUR_JWT_TOKEN
- Register a counselor (no auth)
- Method: POST
- URL:
http://localhost:3000/api/employees/register - Body → raw → JSON:
{
"name": "Alice",
"email": "alice@example.com",
"password": "Password123!"
}
- Expect 201 Created
- Login to get a token
- Method: POST
- URL:
http://localhost:3000/api/employees/login - Body → raw → JSON:
{
"email": "alice@example.com",
"password": "Password123!"
}
- Copy the
tokenfrom the response
- Submit a public enquiry (no auth)
- Method: POST
- URL:
http://localhost:3000/api/enquiries/public - Body → raw → JSON:
{
"name": "John Doe",
"email": "john@example.com",
"courseInterest": "Node.js"
}
- Expect 201 with
{ "id": <number> }
- See public (unclaimed) enquiries (auth required)
- Method: GET
- URL:
http://localhost:3000/api/enquiries/public - Headers:
Authorization: Bearer YOUR_JWT_TOKEN - Expect a list with your unclaimed enquiry, note its
id
- Claim a specific lead (auth required)
- Method: PATCH
- URL:
http://localhost:3000/api/enquiries/<ID>/claim- Replace
<ID>with the numeric id from step 3 or 4
- Replace
- Headers:
Authorization: Bearer YOUR_JWT_TOKEN - Expect success with
claimed: trueandcounselorIdset
- See your private (claimed) enquiries (auth required)
- Method: GET
- URL:
http://localhost:3000/api/enquiries/private - Headers:
Authorization: Bearer YOUR_JWT_TOKEN - Expect to see the enquiry you just claimed
secretOrPrivateKey must have a value→.envis missing orJWT_SECRETnot set; create.env, restart server401 Not authorized→ Missing/expired token; login again and use new token404 Enquiry not found→ Wrongidor different DB; list public enquiries and use a validid409 already claimed→ The enquiry was claimed; try a differentid
- SQLite DB file
crm_db.sqliteis created automatically on first run - Keep your real
.envout of version control; share a.env.exampleinstead