Find file
1eaa1a6 Jun 1, 2016
@tdd @paulmillr
163 lines (112 sloc) 5.77 KB

Web server: built-in or custom

This is part of The Guide.

Earlier in this guide, I mentioned that the watcher also lets you run a web server in the background, to serve the resulting files over HTTP. Some client-side technologies do need to be served over HTTP(S) instead of from a regular file. This also makes for shorter URLs…

There are two ways to run this server:

  • Explicitly through the command line: brunch watch --server, brunch watch -s or even brunch w -s (for that arcane feel);
  • Through the server settings in brunch-config.js.

The built-in server is provided through an npm module named pushserve, and is therefore a bit more than a bare-bones static file server: it offers CORS headers, systematic routing of unknown paths to index.html to make pushState easier, and more.

If you want that server to always run when the watcher starts, you just need to add this to your configuration:

server: {run: true}

If you want a different port than 3333, you can use the -P or --port CLI option, or the server.port setting.

Writing your custom server

This is great already, but sometimes you’ll need a few more features, if only for demo or training purposes… Let’s see how to write our own server, that would provide two REST API endpoints for us:

  • A POST on /items with a title field would add an entry;
  • A GET on /items would obtain the list of entries.

We’ll keep it simple and use good ol’ Express, with the minimum set of modules we need to achieve this.

By default, Brunch will look for a brunch-server.js or file for your custom server module, but you can use a different path with the server.path setting.

This module must directly export a function (default export, using module.exports =) with the following signature:

yourFunction(port, path, callback)

Before Brunch 1.8, you had to export a startServer method on your exported object. This still works, but you should go the simpler way now and export your function directly as the module’s default export.

When your server is up and ready (to serve, ha ha), it calls callback() so Brunch can resume its work. The server is automatically stopped when Brunch’s watcher terminates.

Here’s our example server. I put this in the expected brunch-server.js file.

'use strict';

var bodyParser = require('body-parser');
var express    = require('express');
var http       = require('http');
var logger     = require('morgan');
var Path       = require('path');

// Our server start function
module.exports = function startServer(port, path, callback) {
  var app = express();
  var server = http.createServer(app);

  // We’ll just store entries sent through REST in-memory here
  var items = [];

  // Basic middlewares: static files, logs, form fields
  app.use(express.static(Path.join(__dirname, path)));
  app.use(bodyParser.urlencoded({ extended: true }));

  // GET `/items` -> JSON for the entries array
  app.get('/items', function(req, res) {

  // POST `/items` -> Add an entry using the `title` field'/items', function(req, res) {
    var item = (req.body.title || '').trim();
    if (!item) {
      return res.status(400).end('Nope!');


  // Listen on the right port, and notify Brunch once ready through `callback`.
  server.listen(port, callback);

For this to work, you must first add the necessary modules in your package.json:

$ npm install --save-dev express body-parser morgan

Then we’ll let our brunch-config.js know about it, and make the server auto-run in watch mode, too:

server: {run: true}

Let’s try watching:

$ brunch w
02 Mar 12:45:04 - info: application started on http://localhost:3333/
02 Mar 12:45:04 - info: compiled 3 files into 3 files, copied index.html in 269ms

Notice the custom server info in there. Try loading http://localhost:3333/ in your browser now: it works! In order to test this more thoroughly, let’s adjust our application.js to use the server’s API:

"use strict";

var count = 0;

var App = {
  items: ['Learn Brunch', 'Apply to my projects', '', 'Profit!'],

  init: function init() {
    var tmpl = require('views/list');
    var html = tmpl({ items: App.items });

    $.each(App.items, function(i, item) { requestItem(item); });

function requestItem(item) {
  $.ajax('/items', {
    type: 'post',
    data: { title: item },
    success: function(res) {
      console.log('Successfully posted entry “' + item + '”: ' + res);

      if (++count === App.items.length) {
        $.getJSON('/items', function(res) {
          console.log('Successfully fetched back entries:', res);

module.exports = App;

The watcher picks our update right up, and if we refresh the page and look at the console, we should see this:

Our developer tools console says this works!

All good! (づ ̄ ³ ̄)づ

And not just Node, either…

A final setting you can use is server.command, which basically replaces all the other server settings: it lets you define a custom server-running command line, in case you want to write your own server using another tech, such as PHP, Ruby or Python… You could go something like this:

server: {command: "php -S -t public"}

« Previous: Watcher • Next: Plugins for all your build needs »