Beginner’s Guide to Middleware: Auth and Logging in Express.js

Imagine clicking login on your favorite web app. It checks your details in a flash. You’re in, no hiccups. Ever wonder what makes that magic happen behind the scenes?

Middleware does the heavy lifting. These small code snippets in Express.js run before your main app logic kicks off. They handle jobs like verifying user logins or tracking visits, so your core code stays clean and simple.

You avoid messy repeats, like auth checks on every route. Instead, middleware steps in once, for everyone. Apps run faster and safer as a result.

In this beginner’s guide to middleware, you get the full picture. First, cover the basics of how it fits in Express.js. Then, build authentication middleware with simple steps and code snippets. Next, tackle logging middleware tricks to track errors or user actions. Finally, tie it together in a complete example app.

You’ll follow easy tips any newbie can use. No complex setups needed. Ready to make your app smarter? Let’s start with the fundamentals.

Grasp Middleware Basics to Build Better Apps from Day One

Picture a factory conveyor belt. Raw items enter one end. Stations along the way stamp, paint, or inspect them. Each station passes the item forward. Only then does it reach packaging. Middleware works the same in web apps. It processes requests in sequence before your main code runs.

Requests hit your server. Middleware grabs them first. Each function checks or tweaks the request. It calls next() to hand off to the next one. Finally, your route handler responds. This fits perfectly in the request-response cycle. Order counts too. Stack them first-in, first-out style.

Why bother as a beginner? You reuse code across routes. No more copying auth checks everywhere. Imagine never writing login checks in every route again. Tasks stay separate. Testing gets simple because you isolate pieces. Express.js shines here in Node.js. Python’s Flask does it similarly with decorators.

For visual folks, think of this flow:

Request → Middleware 1 → Middleware 2 → Route Handler → Response
         (log time)    (add header)     (send data)

Benefits stack up fast. Code stays clean. Apps scale better. Auth and logging fit right in as middleware. They run once per request. Prep your mind: we’ll build those next.

See Middleware Flow in a Real Express.js Setup

Let’s prove it with code. Create a file named app.js. Paste this starter. It has two middleware. First logs a timestamp. Second adds a header. A route sends “Hello World”.

const express = require('express');
const app = express();
const port = 3000;

// Middleware 1: Logs request timestamp
app.use((req, res, next) => {
  console.log(`Request at: ${new Date().toLocaleTimeString()}`);
  next(); // Pass to next middleware
});

// Middleware 2: Modifies response with custom header
app.use((req, res, next) => {
  res.set('X-Custom-Header', 'Middleware Magic!');
  next(); // Pass to route handler
});

// Route handler (final stop)
app.get('/', (req, res) => {
  res.send('Hello World from Express!');
});

app.listen(port, () => {
  console.log(`Server runs at http://localhost:${port}`);
});

Req, res, next explained simply: req holds request details like URL or body. res lets you send responses or set headers. next() moves things along. Skip it, and the request hangs.

Run it now. First, make a folder. Run npm init -y. Then npm install express. Finally, node app.js. Hit http://localhost:3000 in your browser.

Watch the console. Timestamp logs first. Then the route fires. Check headers with dev tools. See “X-Custom-Header”. Flow: request enters, middleware 1 logs and calls next, middleware 2 sets header and calls next, route sends response.

This app.use() stacks middleware globally. Add more with app.use(newMiddleware). Order stays key. Swap them, and logs come after headers.

Test it yourself. Tweak the timestamp log. Refresh the page. See changes live. This base readies you for auth checks or error logs ahead. Simple, right?

Secure Your App Effortlessly with Authentication Middleware

You hate copying login checks into every route, right? Authentication middleware solves that pain. It runs once per request, before routes fire. So your controllers stay clean. No repeated if-statements cluttering code.

JWT works best for beginners. It’s a compact token that holds user data. Servers verify it without database hits. That means stateless sessions. Protect private routes easily. Send back 401 Unauthorized on failures. Users get clear errors.

Public routes stay open. Private ones lock down. Middleware checks tokens fast. Common spots include user profiles or admin panels. Let’s build one now. You’ll see how it spots fake users.

Build a Simple JWT Checker Middleware from Scratch

Start simple. First, install the package. Run npm install jsonwebtoken. Now create the middleware. It grabs the token, verifies it, and adds user data to the request.

Here’s the full code. Add it to your app.js. Save a secret key like const JWT_SECRET = 'your-super-secret-key';.

const jwt = require('jsonwebtoken');
const JWT_SECRET = 'your-super-secret-key'; // Use env vars in real apps

const authMiddleware = (req, res, next) => {
  // Step 1: Check for Authorization header
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

  // Step 2: Extract token
  const token = authHeader.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'Invalid token format' });
  }

  try {
    // Step 3: Verify token with secret
    const decoded = jwt.verify(token, JWT_SECRET);
    // Step 4: Attach user to req for routes
    req.user = decoded;
    next(); // All good, proceed
  } catch (error) {
    // Step 5: Handle expired or invalid tokens
    return res.status(403).json({ error: 'Token invalid or expired' });
  }
};

Let me break it down like we’re chatting over coffee. First, grab the header. Most apps send Authorization: Bearer <token>. No header? Boom, 401 error right away.

Split the string for the token part. Empty? Another 401. Now verify with jwt.verify(). It decodes and checks the signature. Matches the secret? Great. Attach req.user so routes access it easily.

Catch errors here. Expired tokens fail silently otherwise. Always return status and JSON. Never call next() on fails. That blocks bad requests.

This differs from login. Login creates tokens with jwt.sign({ id: user.id }, secret). This just checks them. No user lookup needed.

Test it. Need a login endpoint first? Mock one that signs a token. Use Postman. Set header Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.... Hit a test route. Good token passes. Bad one gets 401 or 403. Refresh expired ones to see fails.

Pro tip: Load secrets from .env files. Use dotenv package. Keeps code secure.

Lock Down Routes Using Your New Auth Guard

Ready to protect? Apply the middleware to paths. Use app.use('/api/private', authMiddleware);. Everything under /api/private needs a valid token now.

Add these routes. Public stays open. Private sends user info.

// Public route - no auth
app.get('/api/public', (req, res) => {
  res.json({ message: 'Open to all' });
});

// Protected group
app.use('/api/private', authMiddleware);

app.get('/api/private/profile', (req, res) => {
  res.json({ message: 'Secret data', user: req.user });
});

See the difference? Public hits anytime. Private runs middleware first. Pass? req.user has your data. Fail? Early 401 stops it.

Test with curl. Public works bare:

curl http://localhost:3000/api/public

Protected needs token. Grab one from login. Then:

curl -H "Authorization: Bearer your-jwt-here" http://localhost:3000/api/private/profile

No token? Try without header. Gets 401 JSON.

Watch gotchas. Forget next() in middleware? Request hangs forever. Always return on errors too. No next() there. Order matters. Put auth early in the stack.

Mix public and private smartly. /api/private guards all kids. Single routes? Use app.get('/path', authMiddleware, handler). Scales clean as your app grows.

Track Every Move with Logging Middleware That Does the Heavy Lifting

Ever feel blind to what’s happening in your app? Logging middleware acts like a security camera. It records every request without you lifting a finger. You monitor traffic patterns, spot bugs fast, and meet compliance rules. Middleware catches all requests automatically. No need to add logs in every route. That’s why it beats scattered console.log statements, which miss details and clog your terminal.

Console.log works for quick tests. However, it lacks timestamps, IP addresses, or status codes. Response times stay hidden too. That’s where libraries shine. Morgan handles HTTP logs out of the box for Express.js. Winston offers flexibility for files or JSON. Both save time over custom code. Start with Morgan for beginners. Install it easy: npm install morgan. Then plug it in. Custom versions let you tweak exactly what you need. We’ll compare both next.

Set Up Basic Request and Response Logging

Basic logs track method, URL, status, and duration. They reveal traffic at a glance. Build a custom one first. It mimics Morgan but stays simple.

Create this middleware. Use process.stdout.write to mimic real logs. Add it before routes.

app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    const logEntry = `${req.method} ${req.url} ${res.statusCode} ${duration}ms`;
    process.stdout.write(`${logEntry}n`);
  });
  next();
});

Plug it into your app.js from earlier. Run the server. Hit http://localhost:3000. Check the console. Sample output looks like this:

GET / 200 12ms
GET /api/public 200 8ms
GET /api/private/profile 401 5ms

See the power? GET shows the method. / tracks the URL. 200 means success. 12ms flags slow spots. Custom code gives control. Want IP? Add req.ip. Tweak for your needs.

Prefer ready-made? Morgan does this better. Run npm install morgan. Then:

const morgan = require('morgan');
app.use(morgan('combined')); // Or 'dev' for colors

It logs IP, user-agent, and more. Custom wins for light apps. Morgan scales for production. Pick based on your stack.

Level Up Logs for Errors and Performance Insights

Basic logs miss crashes. Error middleware fixes that. Place it last, after all routes: app.use(errorHandler). It catches unhandled errors safely.

Extend your logger. Calc response time upfront. Log stack traces without exposing them to users. Pull user ID from auth if available.

Here’s an upgraded error handler:

app.use((err, req, res, next) => {
  const start = Date.now();
  console.error('Error:', {
    url: req.url,
    method: req.method,
    status: err.status || 500,
    message: err.message,
    userId: req.user?.id || 'anonymous',
    stack: err.stack, // Safe in dev; filter in prod
    duration: Date.now() - start
  });
  res.status(err.status || 500).json({ error: 'Server error' });
});

Test it. Throw an error in a route: throw new Error('Test crash'). Logs spit JSON like:

{
  "url": "/test",
  "method": "GET",
  "status": 500,
  "message": "Test crash",
  "userId": "123",
  "stack": "...",
  "duration": 3
}

JSON feeds tools like ELK stack. Add custom fields. From auth? req.user.id shows who triggered it.

Pro tip: Rotate logs. Winston supports it. Install npm install winston. Set daily files. Avoid huge console dumps. In production, pipe to files or services.

Performance shines too. Slow requests over 500ms? Flag them. Combine with auth logs. Now you trace issues from login to crash. Your app stays observable.

Combine Auth and Logging in One Slick Express App

Now you have auth and logging pieces ready. Time to merge them into a single app. This setup runs logs on every request first. Then auth guards private paths only. Your app gains eyes everywhere and locks down secrets. Requests flow smooth because order stays right. No more blind spots or open doors. Let’s build a full starter project you can run today.

Build and Run the Complete Starter Project

Grab a new folder. Name it express-middleware-app. Open your terminal there. Run these commands one by one:

  1. npm init -y to create package.json.
  2. npm install express jsonwebtoken morgan dotenv.
  3. Create .env with JWT_SECRET=your-super-secret-key-change-me.
  4. Make app.js with the full code below.

This project includes a login route, public and private paths, global logging, and auth middleware. Logging uses Morgan for colors and details. Copy the code exactly.

require('dotenv').config();
const express = require('express');
const jwt = require('jsonwebtoken');
const morgan = require('morgan');
const app = express();
const port = 3000;
const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret';

app.use(express.json()); // Parse JSON bodies

// Global logging first: tracks everything
app.use(morgan('combined'));

// Auth middleware (from earlier)
const authMiddleware = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }
  const token = authHeader.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'Invalid token format' });
  }
  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(403).json({ error: 'Token invalid or expired' });
  }
};

// Login route: generates token (public)
app.post('/api/login', (req, res) => {
  const { username } = req.body;
  if (!username) {
    return res.status(400).json({ error: 'Username required' });
  }
  const token = jwt.sign({ id: 123, username }, JWT_SECRET, { expiresIn: '1h' });
  res.json({ token });
});

// Public route
app.get('/api/public', (req, res) => {
  res.json({ message: 'Open to everyone' });
});

// Protected routes: logging runs first, then auth
app.use('/api/private', authMiddleware);
app.get('/api/private/profile', (req, res) => {
  res.json({ message: 'Protected data', user: req.user });
});

// Error handler last
app.use((err, req, res, next) => {
  console.error('Error details:', err.stack);
  res.status(500).json({ error: 'Something went wrong' });
});

app.listen(port, () => {
  console.log(`App runs at http://localhost:${port}`);
});

Run node app.js. Test with curl or Postman. First, login: curl -X POST -H "Content-Type: application/json" -d '{"username":"test"}' http://localhost:3000/api/login. Copy the token.

Public works: curl http://localhost:3000/api/public. Logs show in terminal.

Private needs token: curl -H "Authorization: Bearer YOUR_TOKEN_HERE" http://localhost:3000/api/private/profile.

Master Middleware Stacking Rules for Smooth Flow

Stacking follows FIFO order. Global middleware like logging goes first with app.use(). Path-specific auth uses app.use('/path', authMiddleware). Routes come last.

Logging captures auth fails too because it sits upstream. Auth blocks bad tokens before private routes run. Error handler stays at the end. It catches leftovers.

Wrong order hurts. Put auth global? Public routes break. Logging after auth? Misses 401s. Always test flow: request hits log, then auth if protected, then handler.

Spot and Fix Common Issues Fast

Requests hang? Check for missing next() in middleware. Always call it or return res.

No logs? Morgan needs app.use() early. Token fails? Verify secret matches and header format.

Auth runs on public? Scope it to /api/private. Errors not caught? Place handler after all routes.

Tweak and restart. Watch terminal. Fix one issue at a time.

This combo scales big. Maintain code easy because pieces stay separate. Fork this on GitHub. Add your twists. For production, swap secrets to env vars only. Rotate logs with Winston next time. Your app stays sharp.

Conclusion

Middleware in Express.js simplifies your life. It handles authentication and logging without cluttering routes. As a result, your code stays tidy and apps run smooth.

Now put it to work. First, build the starter project we covered. Next, add auth and logging to your own app. Finally, explore tools like Winston for bigger logs.

Share your first middleware in the comments below. Subscribe for advanced guides on scaling Express.js. You’re now ready to handle behind-the-scenes magic like a pro.

Leave a Comment