A comprehensive learning project demonstrating core concepts of building a RESTful API backend with Node.js and Express.js, including authentication, JWT tokens, middleware, routing, MVC architecture, and security best practices.
- Setting up an Express server from scratch
- Understanding the request-response cycle
- Configuring the server to listen on a specific port (3500)
- Using
express.json()andexpress.urlencoded()for parsing request bodies
- Models: Data layer with JSON file storage (
employees.json,users.json) - Controllers: Business logic separated into dedicated controller files
employeesController.js- CRUD operations for employeesregisterController.js- User registration logicauthController.js- Authentication and JWT generation
- Routes: Clean route definitions that delegate to controllers
- Benefits: Better code organization, separation of concerns, easier testing
- CRUD Operations: Create, Read, Update, Delete for employees
- HTTP Methods: GET, POST, PUT, DELETE
- Route Parameters: Using
:idfor dynamic routes - Status Codes: Proper use of 200, 201, 400, 401, 404, 409, 500
- JSON Responses: Consistent API response format
- Password Hashing: Using
bcryptwith salt rounds (10) - JWT (JSON Web Tokens):
- Access tokens (short-lived: 30s)
- Refresh tokens (long-lived: 1d)
- User Registration: Duplicate username detection
- User Login: Password verification with bcrypt
- Environment Variables: Storing secrets in
.envfile
- String Routes vs RegExp Routes: Learned the critical difference between passing route patterns as strings vs RegExp objects
- RegExp Route Syntax: Mastered the pattern
/^\\/route-name(.html)?$/for flexible routing^- Start of the path\\/- Escaped forward slash (literal/)(.html)?- Optional.htmlextension$- End of the path
- Catch-All Routes: Using
/.*/regex for 404 handling instead of the deprecated'*'wildcard
- Logger Middleware: Created a custom logging system that:
- Logs all incoming requests (method, origin, URL)
- Uses
date-fnsfor timestamp formatting - Uses
uuidfor unique request IDs - Writes logs to files asynchronously using
fs.promises
- Error Handler Middleware: Implemented centralized error handling that:
- Logs errors with timestamps and UUIDs
- Returns appropriate error responses
- Uses the middleware chain with
next()
- Configured CORS with a whitelist approach
- Implemented origin validation with callbacks
- Moved CORS configuration to separate config file
- Understood the importance of CORS in API security
- Reading JSON files: Using
require()for initial data loading - Writing JSON files: Using
fs.promises.writeFile()for persistence - Async/Await: Proper async file operations
- Path handling: Using
path.join()for cross-platform compatibility
- In-Memory Data Store: Object with
setEmployees/setUsersmethods - File Persistence: Writing changes back to JSON files
- Data Validation: Checking for required fields and duplicates
- Using
dotenvfor environment variables - Storing sensitive data (JWT secrets) in
.env .gitignoreconfiguration to protect secrets
- Single Route Handlers: Basic request handling
- Multiple Route Handlers: Chaining handlers with
next() - Route Handler Arrays: Passing multiple handlers as an array
[one, two, three] - Router.route(): Chaining HTTP methods on the same route
- Using
req.accepts()to determine client preferences - Serving different content types (HTML, JSON, plain text) based on the
Acceptheader
- Configured
express.static()to serve files from thepublicdirectory - Understanding mount paths for static files
- Understood the middleware order importance
- Nodemon: Auto-restarting the server on file changes
- NPM Scripts: Setting up
startanddevscripts - Git: Version control and
.gitignorebest practices
- Node.js (v24.11.1) - JavaScript runtime
- Express.js (v5.1.0) - Web framework
- bcrypt (v6.0.0) - Password hashing
- jsonwebtoken (v9.0.3) - JWT authentication
- dotenv (v17.2.3) - Environment variables
- cookie-parser (v1.4.7) - Cookie parsing middleware
- CORS (v2.8.5) - Cross-origin resource sharing
- date-fns (v4.1.0) - Date formatting
- uuid (v13.0.0) - Unique ID generation
- Nodemon (v3.1.10) - Development auto-reload
6/
βββ config/
β βββ corsOptions.js # CORS configuration
βββ controllers/
β βββ authController.js # Authentication logic (login, JWT)
β βββ employeesController.js # Employee CRUD operations
β βββ registerController.js # User registration logic
βββ middleware/
β βββ logEvents.js # Custom logging middleware
β βββ errorHandler.js # Error handling middleware
βββ model/
β βββ employees.json # Employee data storage
β βββ users.json # User credentials (hashed passwords)
βββ routes/
β βββ api/
β β βββ employees.js # Employee API routes
β βββ auth.js # Authentication routes
β βββ register.js # Registration routes
β βββ root.js # Root/home routes
β βββ subdir.js # Subdirectory example routes
βββ views/
β βββ index.html # Home page
β βββ 404.html # Custom 404 page
β βββ subdir/ # Subdirectory example
βββ public/
β βββ css/ # Static CSS files
β βββ img/ # Static images
βββ logs/
β βββ reqLog.txt # Request logs
β βββ errLog.txt # Error logs
βββ .env # Environment variables (not in Git)
βββ .gitignore # Git ignore rules
βββ server.js # Main server file
βββ package.json # Dependencies and scripts
- Node.js (v14 or higher)
- npm or yarn
-
Clone the repository
git clone https://github.com/theabdulbasitt/NodeJS_ExpressJS_Backend_Tutorial.git cd NodeJS_ExpressJS_Backend_Tutorial/6 -
Install dependencies:
npm install
-
Create a
.envfile in the root directory:ACCESS_TOKEN_SECRET=your_access_token_secret_here REFRESH_TOKEN_SECRET=your_refresh_token_secret_here PORT=3500
Tip: Generate secure secrets using:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
Development mode (with auto-reload):
npm run devProduction mode:
npm startThe server will run on http://localhost:3500
| Endpoint | Method | Description | Request Body | Response |
|---|---|---|---|---|
/register |
POST | Register a new user | { "user": "username", "pwd": "password" } |
201 - User created400 - Missing fields409 - User exists |
/auth |
POST | Login and get JWT tokens | { "user": "username", "pwd": "password" } |
200 - Login success401 - Invalid credentials |
| Endpoint | Method | Description | Request Body | Response |
|---|---|---|---|---|
/employees |
GET | Get all employees | - | 200 - Array of employees |
/employees |
POST | Create new employee | { "firstname": "John", "lastname": "Doe" } |
201 - Employee created400 - Missing fields |
/employees |
PUT | Update employee | { "id": 1, "firstname": "Jane", "lastname": "Smith" } |
200 - Updated employee400 - Employee not found |
/employees |
DELETE | Delete employee | { "id": 1 } |
200 - Deleted successfully400 - Employee not found |
/employees/:id |
GET | Get employee by ID | - | 200 - Employee object400 - Employee not found |
| Route | Method | Description |
|---|---|---|
/ or /index or /index.html |
GET | Home page |
| Any other route | ALL | Returns 404 (HTML/JSON/Text based on Accept header) |
POST http://localhost:3500/register
Content-Type: application/json
{
"user": "testuser",
"pwd": "securepassword123"
}POST http://localhost:3500/auth
Content-Type: application/json
{
"user": "testuser",
"pwd": "securepassword123"
}POST http://localhost:3500/employees
Content-Type: application/json
{
"firstname": "John",
"lastname": "Doe"
}GET http://localhost:3500/employeesPUT http://localhost:3500/employees
Content-Type: application/json
{
"id": 1,
"firstname": "Jane",
"lastname": "Smith"
}DELETE http://localhost:3500/employees
Content-Type: application/json
{
"id": 1
}Problem: Using regex syntax as a string in routes
app.get('^/$|/index(.html)?', ...) // β Wrong - String with regex syntaxSolution: Use proper RegExp objects
app.get(/^\/$|\/index(.html)?$/, ...) // β
Correct - RegExp objectProblem: Using '*' wildcard for catch-all routes in modern Express
app.all('*', ...) // β Causes PathError in Express v5+Solution: Use regex pattern instead
app.all(/.*/, ...) // β
Works reliably across versionsProblem: Case-sensitive typo in controller method name
.get(employeesController.getAllemployees) // β Wrong - lowercase 'e'Solution: Match exact function name
.get(employeesController.getAllEmployees) // β
Correct - uppercase 'E'Problem: Setting status without sending response
if (duplicate) return res.status(409); // β No response sentSolution: Always send a response
if (duplicate) return res.status(409).json({ "message": "User exists" }); // β
Problem: JSON IDs as strings causing concatenation instead of addition
id: data.employees[data.employees.length - 1].id + 1 // β "2" + 1 = "21"Solution: Parse string to number first
id: parseInt(data.employees[data.employees.length - 1].id) + 1 // β
2 + 1 = 3Problem: Empty JSON file
users: require('../model/users.json') // β File is emptySolution: Initialize with empty array
[]The order of middleware is crucial in Express:
- Logger - Logs all requests
- CORS - Handles cross-origin requests
- Body Parsers - Parse URL-encoded and JSON data
- Static Files - Serve files from
public/ - Route Handlers - Handle specific routes
- 404 Handler - Catch-all for unmatched routes
- Error Handler - Handle errors (must be last)
- MVC Architecture: Separate concerns into Models, Controllers, and Routes
- Security First: Hash passwords, use JWT, validate input, protect secrets
- Error Handling: Always send proper HTTP status codes and error messages
- Async/Await: Use for all asynchronous operations
- Environment Variables: Never commit secrets to Git
- Code Organization: Use separate files for configuration, controllers, and routes
- Data Validation: Check for required fields and duplicates
- RESTful Design: Follow REST conventions for API endpoints
- Path Handling: Use
path.join(__dirname, ...)for cross-platform compatibility - RegExp Routes: Use RegExp objects for complex route patterns in modern Express
- Content Negotiation: Implement for better API design
- Proper Status Codes: Use 200, 201, 400, 401, 404, 409, 500 appropriately
- β Passwords hashed with bcrypt (salt rounds: 10)
- β JWT tokens for authentication
- β Environment variables for secrets
- β
.envfile excluded from Git - β CORS whitelist for origin validation
- β Input validation for all endpoints
β οΈ Note: This is a learning project. For production:- Use a real database (MongoDB, PostgreSQL)
- Implement refresh token rotation
- Add rate limiting
- Use HTTPS
- Implement proper session management
- Add input sanitization
- Express.js Documentation
- Node.js Documentation
- JWT.io
- bcrypt Documentation
- path-to-regexp Documentation
- CORS Documentation
ISC
Author: Abdul Basit
Repository: NodeJS_ExpressJS_Backend_Tutorial
Version: 2.0.0