Alpha preview

Bareserver — Express.js alternative for Minimalists

Bareserver is an extremely simple and fast web server for Node. It's a fresh, new way to build RESTful services.

Created: Jan. 23, 2020

Edited: Jan. 24, 2020: added contexts and file uploads

// start on port 8080
const server = require('bareserver')(8080)

// naive routing example
server.get('/ping', function() {
  return 'pong'

// something useful'/users', async function(args) {
  return await app.addUser(args)

// image uploads'/images', async function(img) {
  await fs.promises.writeFile('images', img)

NPM dependencies

Lines of code

API size

Runtime performance

Startup time

Why Bareserver?

Bareserver offers a simple, domain-specific syntax for creating RESTful web services.

The primary reason for building Bareserver was to get an API that makes web services feel like local development: you take in arguments and send back return values. There are no request and response objects so you never need to worry about request bodies, argument parsing or return value serialization. And you don't need a separate middleware to get those async calls working.

The small API documentation fits into two pages. You learn to use Bareserver it in minutes, and you rarely need to go back for docs when building services.

Bareserver starts immediately and the runtime is extremely fast because it's a very thin wrapper above the raw Node HTTP interface.

Bareserver is opinionated — it is optimized for one specific purpose: creating RESTful web services. Express, on the other hand, is unopinionated: it is more flexible, more configurable, and has more features. However, this comes with an expense: Express.js API documentation is 53 sheets of paper when printed.

Bareserver is used extensively on this website. We currently have 91 routes defined on our CRM. We even used it as the frontend to our analytics service for 38 different websites with millions of requests on a single day. It never crashed. A small codebase is easy to stabilize.


The verbs

All the HTTP verbs, like get, post, put, delete, patch are supported.

server.delete('/account', async function() {
  await this.user.remove()

Pattern matching

Get additional arguments from the path itself.

// --> POST: /mailing-lists/backend-devs'/mailing-lists/%s', async function(list_name) {
  await this.user.addToMailingList(list_name)


Define contexts for nested routes and shared variables.

// create /account context
const ctx = server.context('/account')

// access control
ctx.all(async function(headers) {

  const userId = await sessions.get(headers.session_id)
  if (!userId) throw { 401: 'invalid_session' }

  // add user variable to the context
  this.user = await app.getUserById(userId)
  if (!this.user) throw { 401: 'invalid_user' }


// nested routes
ctx.get(async function() {
  return await this.user.getAccount()
})'/email', function(email) {
  return await this.user.setEmail(email)

Global response manipulation

Manipulate the return value globally before sending it back to the client.

server.after(function(path, data) {
  data.user = user

File uploads

Multipart requests are automatically detected'/avatar', async function(avatar) {
  return await this.user.addAvatar(avatar)

Credits to busboy for doing the heavy lifting, which is the only NPM dependency in the alpha version. It's optional if you don't need file uploads.

Global error handling

Handle user- and system errors in one central place

server.error(async function(error) {

  // skip user errors
  if (error.status) return

  // save for inspection
  await app.pushError(error)

  // continue operating without crashing
  error.status = 500


Raw routes

Use request to bypass Bareserver and get raw access to the Node's http.ClientRequest and http.ServerResponse objects. Good for non-REST communication like WebSockets and Server-Sent Events (SSE).

// send live updates with SSE
server.request('/live-updates', function(req, res) {

  if (req.headers.accept == 'text/event-stream') {
    res.writeHead(200, {
      'Access-Control-Allow-Origin': '*',
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive'


Here's a primitive “hello world” benchmark aiming to evaluate the framework overhead.

server.get('/', async function() {
  return { hello: 'world' }

Machine: 1,2 GHz Intel Core m5, 8 GB 1867 MHz LPDDR3

Method: autocannon -c 100 -d 40 -p 10 localhost:3000

Bareserver:   27,448 request per second
Fastify:      22,768 request per second
Express:      13,370 request per second

For each server, we ran the benchmark two times and took the average from the second run. However, those are still ballpark figures since we had quite a bit of variance on the benchmark results. Also worth noting that server benchmarks tend to favor their solution. For example, Fastify and Foxify beat each other on their benchmarks.

Regardless of the results, Bareserver is definitely one of the top performers because it's such a thin wrapper above the raw Node HTTP interface.

Request access

Bareserver is private alpha, but beta-testers are welcome.


Please note that it takes some time before you get the access. There's some work left before this is in beta stage.

Frequently Asked Questions

Where is this coming from?

Bareserver is heavily inspired by a 12-year old Ruby project Sinatra. Bareserver brings their powerful DSL idea for Node developers. Big thanks to Sinatra's inventor rtomayko.

What's up with the word “opinionated”?

Opinionated design is at the heart of minimalism. You must make choices to make a great solution to a specific problem. REST and JSON are great choices on our opinion. Graph QL might also be good, but not in our opinion.

Why should I care about the lines of code?

Large codebases are harder to extend and keep in control. They are more complex so it takes a longer time to fix issues. Complexity is also related to performance: the more to execute, the slower it gets.

How is this different from Fastify?

Fastify is another unopinionated framework with 3,516 lines of code, 58 npm dependencies, and 122 plugins. We think Fastify is very similar to Express.

How about Restana, Restify, Koa or Polka?

Same thing. They embrace unopinionated design and are designed for many different use cases.

Why not use arrow functions on the examples?

We think old school function declarations are clearer, especially with the async keyword. Explicit is good. Or maybe we are just old farts.

Why not give GitHub access now?

The private version of Bareserver is in active use on this website, but we're just getting started with the open source version. The primary reason for writing this article is to collect initial feedback and validate the demand.

Eventually, if there is interest, we want to make a solid release with good documentation, Long Term Support (LTS), prepare resources for support and issues, setup contribution guidelines and figure out a license.

Who are you?

We are developers of Volument, which is a new take on website analytics and A/B testing. We think the current model is broken and needs to be fixed.

Where is this project heading?

Towards stability. We strive for great API, handy syntax, zero issues and Long Term Support. Feature richness is not our goal.


View all blog entries


If you like Bareserver, then you might also like Baretest, which is a Jest alternative for minimalists.

A new, minimalistic way to run tests

View all blog entries

{"og_image":"/blog/img/bareserver-hero.png","slogan":"A/B testing for hackers","greeting":"Ditching Express","date":"2020-01-23T01:00","script":"/blog/index","style":["/blog/pagestyle/bareserver","/learn/syntax"],"desc":"Bareserver is an extremely simple and fast web server for Node. It's a fresh, new way to build RESTful services.","title":"Bareserver — Express.js alternative for Minimalists","url":"/blog/bareserver-express-alternative-for-minimalists","key":"bareserver-express-alternative-for-minimalists","created":"2020-02-14T16:04:05.969Z","modified":"2020-02-14T16:04:05.969Z","createdISO":"2020-02-14","modifiedISO":"2020-02-14"}