Back to List

Serving Static Files and Handling Errors

Learn how to serve HTML, CSS, and images in Express, and handle 404 and 500 errors gracefully.

Intermediate
|
10min
|
Verified (2026-07)
static filesExpresserror handling404 pagemiddleware
Progress0/55 (0%)

Serving Static Files and Handling Errors

After completing this topic

You'll be able to serve static files like HTML, CSS, and images in Express, and handle 404 (Not Found) and 500 (Server Error) errors gracefully.


What are static files?

There are two types of files that a web server handles:

  • Dynamic files: Files that the server generates by executing code for each request (e.g., APIs, database query results).
  • Static files: Files that the server simply delivers as is (e.g., HTML, CSS, JavaScript, images, fonts).

In a blog, the list of articles might be dynamic, but the blog's logo image and stylesheet are static. Static files don't need to be created anew for each request, so they are efficiently handled by a separate middleware.


express.static - Static file middleware

Express has a built-in express.static middleware.

text
project/
β”œβ”€β”€ app.js
β”œβ”€β”€ public/
β”‚   β”œβ”€β”€ index.html
β”‚   β”œβ”€β”€ css/
β”‚   β”‚   └── style.css
β”‚   β”œβ”€β”€ js/
β”‚   β”‚   └── main.js
β”‚   └── images/
β”‚       └── logo.png
javascript
const express = require('express');
const path = require('path');
const app = express();

// Set the 'public' folder as the root for static files
app.use(express.static(path.join(__dirname, 'public')));

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Now, if you access http://localhost:3000/css/style.css in your browser, the public/css/style.css file will be returned. You don't need to include /public in the URL because express.static maps the public folder to the root.

Adding a URL prefix

javascript
// Access via `/assets/css/style.css`
app.use('/assets', express.static(path.join(__dirname, 'public')));

The first argument is the URL prefix. This way, although the actual file path is public/css/style.css, the URL will be /assets/css/style.css. This is useful for separating paths for CDNs or versioning.

Specifying multiple folders

javascript
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'uploads')));

You can register both folders as static file sources. If the first folder cannot find the file, it will search the second one. The order determines the priority.


path.join and __dirname - Making paths safe

javascript
// Bad: Relative path - depends on the execution location
app.use(express.static('public'));

// Good: Absolute path - consistent regardless of execution location
app.use(express.static(path.join(__dirname, 'public')));

__dirname is the absolute path of the directory where the current file is located. path.join() automatically handles the appropriate path separator for the operating system (/ or \). Always use path.join to avoid problems with directly using / in Windows.


Handling 404 - Not Found

In Express, if a request doesn't match any routes, the request is simply dropped, leaving the client waiting indefinitely. To prevent this, you must place a 404 handler after all other routes.

javascript
// Define routes
app.get('/', (req, res) => {
  res.send('Home');
});

app.get('/about', (req, res) => {
  res.send('About');
});

// 404 handler - after all routes, before error handlers
app.use((req, res) => {
  res.status(404).json({
    error: 'Not Found',
    message: `${req.method} ${req.url} does not exist`,
    status: 404
  });
});

The order is important. Because app.use() executes in order, placing the 404 handler at the beginning will cause all requests to return a 404. It must be placed after all route definitions.

Serving an HTML 404 page

If you're not serving an API but a website, it's more natural to display an HTML page instead of JSON.

javascript
app.use((req, res) => {
  res.status(404).sendFile(path.join(__dirname, 'public', '404.html'));
});

Handling 500 - Internal Server Error

Error handling middleware has four parameters. Express uses this signature to distinguish between regular middleware and error handlers.

javascript
// Error handler - after the 404 handler
app.use((err, req, res, next) => {
  console.error(`[ERROR] ${err.stack}`);
  
  res.status(err.status || 500).json({
    error: 'Internal Server Error',
    message: process.env.NODE_ENV === 'production'
      ? 'Something went wrong'
      : err.message,
    status: err.status || 500
  });
});

Two things to note:

  1. In production, hide error messages. This is because err.message may contain sensitive information such as SQL queries or file paths. Only show detailed messages in development environments.

  2. How to throw errors: Calling next(err) in a route will jump to the error handler.

javascript
app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await findUser(req.params.id);
    if (!user) {
      const err = new Error('User not found');
      err.status = 404;
      return next(err);
    }
    res.json(user);
  } catch (err) {
    next(err); // Unexpected errors like database errors
  }
});

Overall structure - correct order

javascript
const express = require('express');
const path = require('path');
const app = express();

// 1. Basic middleware
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

// 2. Routes
app.get('/api/users', (req, res) => { /* ... */ });
app.post('/api/users', (req, res) => { /* ... */ });

// 3. 404 handler (after routes)
app.use((req, res) => {
  res.status(404).json({ error: 'Not Found' });
});

// 4. Error handler (always last)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Internal Server Error' });
});

app.listen(3000);

This order is the standard structure for an Express project. Basic middleware β†’ routes β†’ 404 β†’ error. Deviating from this order will lead to unexpected behavior.


Common mistakes in practice

MistakeSymptomSolution
404 handler before routesAll requests return 404Move 404 to the end
Error handler with 3 parametersErrors are not caughtUse 4 parameters: (err, req, res, next)
Relative path for express.staticFiles are not found when running from a different folderUse path.join(__dirname, ...)
Not calling next(err) in error handlingError handler is not reachedUse try/catch + next(err)
Exposing err.stack in productionSecurity vulnerabilityUse NODE_ENV branching

Key takeaways

Serving static files and handling errors are about handling both "when the server is normal" and "when it's not." express.static efficiently serves files, and 404/500 handlers ensure that meaningful responses are provided even in exceptional situations. Remembering the middleware order in Express (parsing β†’ static β†’ routes β†’ 404 β†’ error) will prevent confusion about where to place things.

πŸ’¬ Questions & Comments

0 comments

You can post without signing in. Guest comments cannot be edited or deleted by their author.

0/2000

Loading...