esbuild’s serve mode doesn’t just rebuild your code; it actually serves it directly from memory, bypassing your filesystem for lightning-fast updates.

Here’s a quick look at esbuild in action, serving a simple HTML file with some JavaScript:

First, create an index.html file:

<!DOCTYPE html>
<html>
<head>
  <title>esbuild Serve Example</title>
</head>
<body>
  <h1>Hello, esbuild Serve!</h1>
  <script src="./index.js"></script>
</body>
</html>

Next, create an index.js file:

console.log('JavaScript loaded and running!');
document.body.innerHTML += '<p>Content updated by JavaScript!</p>';

Now, set up a basic esbuild.js configuration file:

const esbuild = require('esbuild');

esbuild.serve({
  servedir: '.', // Serve files from the current directory
  port: 8000,
  host: 'localhost',
  fallback: 'index.html', // Fallback for SPA routing
  onRequest: (args) => {
    console.log(`Request received: ${args.method} ${args.path}`);
  }
}, {
  entryPoints: ['index.js'],
  bundle: true,
  outfile: 'bundle.js', // Output file (though serve mode primarily uses memory)
  platform: 'browser',
  format: 'iife',
  sourcemap: true,
}).catch(() => process.exit(1));

console.log('esbuild serve mode started on http://localhost:8000');

Run this configuration using Node.js:

node esbuild.js

Open your browser to http://localhost:8000. You’ll see "Hello, esbuild Serve!" and "Content updated by JavaScript!". The console in your terminal will log each request.

Now, change index.js to:

console.log('JavaScript updated!');
document.body.innerHTML += '<p>Content updated again!</p>';

Save the file. Within milliseconds, refresh your browser. You’ll see the updated messages. The terminal will show the new request logs.

The core problem esbuild serve solves is the developer experience bottleneck caused by slow server rebuilds and file system I/O. Traditional build tools often involve writing compiled assets to disk, which can be a significant overhead, especially for large projects. esbuild bypasses this by keeping the entire application bundle in memory. When a file changes, esbuild’s Go-based core can recompile the affected modules and update the in-memory representation of your bundle almost instantly. The serve command then uses a lightweight, in-memory HTTP server to deliver these updated assets directly to the browser. This avoids disk reads and writes entirely during the development cycle. The servedir option tells esbuild where to find your static assets (like index.html), and fallback is crucial for single-page applications, ensuring that any route request that doesn’t match a file is directed to your index.html, allowing client-side routing to take over.

The onRequest hook is a simple but powerful way to observe what’s happening on the server. It fires for every incoming HTTP request, giving you access to the request method and path. This can be useful for debugging or for implementing custom server-side logic during development, though its primary purpose in serve mode is observability.

What most developers don’t realize is that the outfile option in the esbuild.serve configuration, while still necessary for esbuild to know what to eventually output, is largely ignored by the serve command itself. The compiled JavaScript and CSS are held entirely in memory and served directly. This is the key to its speed; there’s no need to wait for the file system to be written to and then read from again by the HTTP server.

The next logical step is to integrate this fast development server into a more complex project, perhaps one using a frontend framework like React or Vue, and explore how esbuild handles module resolution and dependency management in that context.

Want structured learning?

Take the full Esbuild course →