🚀 Node.js/Express Server Setup: Complete Technical Documentation
1. Code Explanation
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello from localhost:3000!');
});
app.listen(PORT, () => {
console.log(`✅ Server running at http://localhost:${PORT}`);
});
| Line | Component | Explanation |
|---|---|---|
| 1 | require('express') |
Imports the Express.js framework into the module. Express is a minimal and flexible Node.js web application framework that provides HTTP utility methods and middleware. |
| 2 | express() |
Instantiates the Express application by invoking the top-level function exported by the express module. Returns an app object containing methods for routing, middleware, and server configuration. |
| 3 | PORT |
Defines the server port using the environment variable pattern. process.env.PORT reads from system environment variables (used by hosting platforms like Heroku, Render, AWS). || 3000 provides a fallback default when no environment variable is set. |
| 5-7 | app.get() |
Defines a route handler for HTTP GET requests to the root path (/). req contains request data (headers, query parameters, body). res contains response methods (send(), json(), status()). res.send() returns an HTTP response with the specified string body. |
| 9-11 | app.listen() |
Binds the server to the specified port and starts listening for incoming connections. The callback executes once the server is successfully bound to the port. PORT variable determines which port is used. |
2. Dependencies
| Dependency | Version | Purpose | Installation Command |
|---|---|---|---|
| express | ^4.18.2 | Web framework providing routing, middleware, HTTP utilities | npm install express |
| nodemon (dev) | ^3.0.0 | Development tool that auto-restarts the server when file changes are detected | npm install -D nodemon |
Why these versions
Express 4.x is stable and mature. Nodemon 3.x supports modern Node.js versions (18+).
3. Package.json Configuration
{
"name": "my-express-app",
"version": "1.0.0",
"description": "Express server application",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
Scripts explained
npm start → Production entry point; runs the server with Node.jsnpm run dev → Development mode; runs with Nodemon for auto-restart
4. Environment Variables
| Variable | Default | Description | Example |
|---|---|---|---|
PORT |
3000 | TCP port the server listens on | PORT=8080 node server.js |
NODE_ENV |
development | Runtime environment; affects logging, caching, error handling | NODE_ENV=production node server.js |
# .env - Do not commit this file
PORT=3000
NODE_ENV=development
LOG_LEVEL=debug
require('dotenv').config(); // Loads .env into process.env
const PORT = process.env.PORT || 3000;
5. Request-Response Flow Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXPRESS REQUEST-RESPONSE FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Client (Browser/curl/Fetch) │
│ │ │
│ │ HTTP Request: GET / │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ EXPRESS SERVER │ │
│ │ │ │
│ │ 1. Request enters → Express app() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 2. Middleware stack (if any) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 3. Route matching (app.get('/')) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 4. Route handler executes │ │
│ │ │ │ │
│ │ │ res.send('Hello from localhost:3000!') │ │
│ │ ▼ │ │
│ │ 5. Response generated │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ HTTP Response: 200 OK + Body │
│ ▼ │
│ Client receives response │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
6. Common Route Methods
| Method | HTTP Verb | Use Case | Example |
|---|---|---|---|
app.get() |
GET | Retrieve data (idempotent, no side effects) | app.get('/users', getUsers) |
app.post() |
POST | Create new data | app.post('/users', createUser) |
app.put() |
PUT | Replace entire resource | app.put('/users/:id', replaceUser) |
app.patch() |
PATCH | Partial update | app.patch('/users/:id', updateUser) |
app.delete() |
DELETE | Remove data | app.delete('/users/:id', deleteUser) |
app.use() |
ALL | Middleware for all HTTP methods | app.use(express.json()) |
app.route() |
Multiple | Chain multiple methods on same path | app.route('/users').get(fn).post(fn) |
7. Extended Server Examples
With JSON Body Parsing Middleware
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post('/api/data', (req, res) => {
const receivedData = req.body;
res.status(201).json({
message: 'Data received',
data: receivedData,
timestamp: new Date().toISOString()
});
});
With Central Error Handling
app.get('/users/:id', async (req, res, next) => {
try {
const user = await db.findUser(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
} catch (error) {
next(error);
}
});
app.use((err, req, res, next) => {
console.error(`[ERROR] ${err.stack}`);
const status = err.status || 500;
const message = process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message;
res.status(status).json({ error: message, timestamp: new Date().toISOString() });
});
With Environment-Specific Configuration
if (process.env.NODE_ENV === 'development') {
app.use(require('morgan')('dev'));
app.use(require('cors')());
}
if (process.env.NODE_ENV === 'production') {
const helmet = require('helmet');
const compression = require('compression');
app.use(helmet());
app.use(compression());
}
app.get('/health', (req, res) => {
res.status(200).json({
status: 'healthy',
uptime: process.uptime(),
timestamp: new Date().toISOString()
});
});
8. Production Considerations
| Consideration | Development | Production |
|---|---|---|
| Port binding |
127.0.0.1 (localhost only)
|
0.0.0.0 (accept external connections) or container orchestration
|
| Process manager |
nodemon (auto-restart on changes)
|
pm2, systemd, Docker (managed restart, clustering)
|
| Logging |
console.log / console.error (simple)
|
Structured logging (Winston, Pino, Bunyan) with log aggregation |
| Error handling | Verbose stack traces, full error details | Sanitized error messages (no stack traces to clients) |
| Graceful shutdown | Not required | Required (close connections, drain requests on SIGTERM) |
| Environment variables | Loaded from .env file |
Injected by orchestrator (K8s Secrets, AWS Secrets Manager) |
| Static files | Served directly | Served via CDN or reverse proxy (Nginx) |
| Concurrency | Single-threaded (default) | Cluster mode (pm2 or Node.js cluster module) |
9. Graceful Shutdown Implementation
const server = app.listen(PORT, () => {
console.log(`✅ Server running at http://localhost:${PORT}`);
console.log(` Environment: ${process.env.NODE_ENV || 'development'}`);
console.log(` Process ID: ${process.pid}`);
});
const shutdown = (signal) => {
console.log(`\n⚠️ ${signal} received, starting graceful shutdown...`);
server.close(() => {
console.log('✅ HTTP server closed');
console.log('✅ Graceful shutdown complete');
process.exit(0);
});
setTimeout(() => {
console.error('❌ Force shutdown timeout exceeded');
process.exit(1);
}, 15000);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('uncaughtException', (error) => {
console.error('❌ Uncaught Exception:', error);
shutdown('UNCAUGHT_EXCEPTION');
});
process.on('unhandledRejection', (reason) => {
console.error('❌ Unhandled Rejection:', reason);
shutdown('UNHANDLED_REJECTION');
});
10. Testing the Server
| Test | Command | Expected Output |
|---|---|---|
| Start server (default port) | npm start |
✅ Server running at http://localhost:3000 |
| Start server (custom port) | PORT=3001 node server.js |
✅ Server running at http://localhost:3001 |
| Development mode | npm run dev |
Server starts with nodemon, auto-restarts on file changes |
| Health check | curl http://localhost:3000/ |
Hello from localhost:3000! |
| Check port status | lsof -i :3000 |
Shows process using port 3000 |
11. Common Errors and Solutions
| Error | Cause | Solution |
|---|---|---|
Error: listen EADDRINUSE: address already in use :::3000 |
Port already occupied |
npx kill-port 3000 or kill -9 <PID>
|
Error: Cannot find module 'express' |
Express not installed | npm install express |
Cannot GET / |
No route handler defined | Add app.get('/') handler |
TypeError: app.listen is not a function |
App not instantiated |
const app = express() not express
|
TypeError: req.body is undefined |
JSON middleware missing | Add app.use(express.json())
|
CORS policy error |
Cross-origin request blocked | Add cors() middleware: npm install cors
|
12. Best Practices Checklist
Security & Configuration
☐ Use environment variables for configuration
☐ Never commit
☐ Add
☐ Implement centralized error handling middleware
☐ Add graceful shutdown for production
☐ Set
☐ Never commit
.env files to version control☐ Add
express.json() middleware for JSON payloads☐ Implement centralized error handling middleware
☐ Add graceful shutdown for production
☐ Set
NODE_ENV=production in productionDevelopment & Tooling
☐ Use
☐ Add
☐ Use
nodemon for development auto-restart☐ Add
morgan logging middleware☐ Use
dotenv for local environment variablesProduction Hardening
☐ Add health check endpoint (
☐ Implement request logging (structured logs)
☐ Add rate limiting (
☐ Use compression middleware
☐ Add security headers (
☐ Set
/health)☐ Implement request logging (structured logs)
☐ Add rate limiting (
express-rate-limit)☐ Use compression middleware
☐ Add security headers (
helmet)☐ Set
trust proxy to true behind reverse proxySummary
Quick Reference Commands
npm init -y → Create package.jsonnpm install express → Install Expressnpm install -D nodemon → Install development auto-restartnode server.js → Run production servernpm run dev → Run development server with auto-restartPORT=8080 node server.js → Run on custom port
This basic Express server provides the foundation for any Node.js web application. The pattern of requiring the module, instantiating the app, defining routes, and listening on a port is universal across Express projects. The key extensions—environment variables, error handling, graceful shutdown, and production hardening—transform this simple script into production-ready infrastructure.
No comments:
Post a Comment