This tutorial shows how to create a Node.js server with Express.js in JavaScript, implementing Basic Authentication to secure a /user
endpoint. The server authenticates users against a userlist.json
file, supports CORS for a React Client at localhost:5173
, and mimics the functionality of a TypeScript HTTP server. Perfect for learning Express and authentication!
Prerequisites
- Node.js (LTS, e.g., 20.x): Install from nodejs.org and verify:
node -v npm -v
- Text editor: VS Code or IntelliJ.
- Terminal: For commands.
- Angular client (optional): Running at
http://localhost:5173
(see Building a Simple React App with Basic Authentication).
Step 1: Set Up the Project
Create a project folder and initialize it:
mkdir nodejs-express-auth
cd nodejs-express-auth
npm init -y
Install Express.js:
npm install express
Create server.js:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Optional: Configuring nodemon and browser-sync for Live Reloading
Now that you’ve built your Express “Hello World” server, you might have noticed that changes to server.js
require a manual restart and browser refresh. To streamline development, we can configure nodemon
for server-side hot reloading and browser-sync
for automatic browser refreshes. This chapter guides you through the setup, perfect for enhancing your productivity as a developer working on Node.js applications.
Why Use nodemon and browser-sync?
- nodemon: Monitors your Node.js files and restarts the server automatically when changes are detected, saving you from manual restarts.
- browser-sync: Proxies your Express server and reloads the browser when files change, providing a seamless development experience.
- Together, they’re ideal for rapid iteration, aligning with modern development practices you might use in Angular or AWS deployments.
Install Required Packages
Install nodemon
, browser-sync
, and concurrently
(to run both tools simultaneously):
npm install --save-dev nodemon browser-sync concurrently
- Add
--save-dev
to include them indevDependencies
inpackage.json
.
Update package.json
Modify your package.json
to include a development script:
{
"scripts": {
"start": "node server.js",
"dev": "concurrently \"npx nodemon server.js\" \"browser-sync start --proxy http://localhost:3000 --files server.js\""
}
}
"start"
: Runs the server normally."dev"
: Usesconcurrently
to runnodemon
(vianpx
to avoid global installation) andbrowser-sync
together.--files server.js
: Tellsbrowser-sync
to watch onlyserver.js
for changes (adjust if monitoring multiple files).
Run the Development Server
Start the setup with:
npm run dev
- Expect output like:
[0] [nodemon] starting
server.js“[1] [Browsersync] Proxying: http://localhost:3000
[1] Local: http://localhost:3001
(browser-sync UI)
- Open
http://localhost:3001
in your browser to see the proxied server.
Test Live Reloading
- Edit
server.js
(e.g., changeres.send('Hello World!')
tores.send('Hello Updated World!')
):
const express = require('express'); const app = express(); const port = 3000; app.get('/', (req, res) => { res.send('Hello Updated World!'); }); app.listen(port, () => { console.log(`Server running at http://localhost:${port}`); });
- Save the file.
nodemon
will restart the server, andbrowser-sync
will refresh the browser automatically.
Troubleshooting Common Issues
- “nodemon: command not found”:
- Ensure
nodemon
is installed. If usingnpx
fails, install globally:npm install -g nodemon
- Then update the script to
"dev": "concurrently \"nodemon server.js\" \"browser-sync start --proxy http://localhost:3000 --files server.js\""
.
- Ensure
- Browser Not Reloading:
- Verify
browser-sync
is proxying the correct port (e.g., matchport
inserver.js
). - Check the
--files
flag; use--files *.js
to watch all.js
files if needed.
- Verify
- Port Conflict:
- If port 3000 is in use, change it in
server.js
(e.g.,const port = 8080
) and update the--proxy
flag.
- If port 3000 is in use, change it in
- Deprecation Warnings:
- Ignore
[DEP0060]
frombrowser-sync
for now; update to the latest version (npm install browser-sync@latest
) if persistent.
- Ignore
Optional Enhancements
- Custom nodemon Configuration:
- Create
nodemon.json
to watch specific files:{ "watch": ["server.js"], "ext": "js" }
- Create
- Multiple File Watching:
- Adjust
--files
tosrc/*.js
if you moveserver.js
to asrc
folder later.
- Adjust
- Docker Integration:
- For AWS deployment, add
nodemon
to yourDockerfile
:RUN npm install -g nodemon CMD ["npm", "run", "dev"]
- For AWS deployment, add
Step 2: Create the User List
Create userlist.json
in the project root:
[
{
"username": "admin",
"password": "password123",
"role": "admin"
},
{
"username": "user",
"password": "secret",
"role": "user"
}
]
Step 3: Write the Server Code
Update server.js
:
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
console.log('Express application initialized');
const userListPath = path.join(__dirname, 'userlist.json');
console.log('User list path set to:', userListPath);
// Middleware for CORS (handles preflight OPTIONS)
app.use((req, res, next) => {
console.log('CORS middleware triggered for request:', req.url);
if (req.method === 'OPTIONS') {
console.log('Handling OPTIONS preflight request for:', req.url);
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:5173');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type');
return res.status(204).end();
}
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:5173');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type');
next();
});
// Authentication middleware (unchanged)
const authenticate = (req, res, next) => {
console.log('Authentication middleware started for request:', req.url);
const authHeader = req.headers.authorization;
if (!authHeader) {
console.log('No authorization header found');
res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"');
return res.status(401).end();
}
console.log('Authorization header received:', authHeader);
const base64Credentials = authHeader.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
console.log('Decoded credentials:', credentials);
const [username, password] = credentials.split(':');
let users = [];
try {
console.log('Attempting to read userlist.json');
const data = fs.readFileSync(userListPath, 'utf8');
users = JSON.parse(data);
console.log('User list loaded successfully:', users);
} catch (err) {
console.error('Error reading userlist.json:', err);
return res.status(500).send('Server error');
}
console.log('Searching for user:', username);
const user = users.find(u => u.username === username && u.password === password);
if (!user) {
console.log('No matching user found for:', username);
res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"');
return res.status(401).end();
}
console.log('User authenticated:', user);
req.user = { username: user.username, role: user.role };
next();
};
// Routes
app.get('/user', authenticate, (req, res) => {
console.log('Processing /user route for user:', req.user.username);
res.json({ username: req.user.username, role: req.user.role });
});
app.get('/', (req, res) => {
console.log('Processing / route');
res.send('Public page: Hello World');
});
const PORT = 3000;
app.listen(PORT, () => {
console.log('Server listening on port:', PORT);
console.log(`Server running at http://localhost:${PORT}/`);
});
What’s Happening?
- Express: Simplifies routing and middleware compared to raw HTTP.
- CORS: Allows Angular at
localhost:4200
to connect. - Authentication: Checks
Authorization
header (Basic <Base64(username:password)>
) againstuserlist.json
. - Routes:
/user
: Protected, returns{ username, role }
./
: Public, returns “Hello World”.
- Preflight: Handles
OPTIONS
requests for CORS.
Step 4: Run the Server
Start the server:
node server.js
See: Server running at http://localhost:3000/
.
Step 5: Test the Server
- Browser:
- Visit
http://localhost:3000/
→ “Public page: Hello World”. - Visit
http://localhost:3000/user
→ Enteradmin:password123
→{"username":"admin","role":"admin"}
. Wrong credentials show “Invalid credentials”.
- Visit
- cURL:
curl http://localhost:3000
# Public: "Public page: Hello World"
curl -u admin:password123 http://localhost:3000/user
# Secure: {"username":"admin","role":"admin"}
curl http://localhost:3000/user
# No auth: "Authentication required"
Security Notes
- JSON File: Replace with a database (e.g., PostgreSQL) in production.
- HTTPS: Use HTTPS to secure Basic Auth credentials.
- Error Handling: Add robust validation for
userlist.json
. - Rate Limiting: Use
express-rate-limit
to prevent brute-force attacks.