Documentation

Documentation

Conceptually server is a function that accepts options and other functions. The heavy lifting is already implemented so you can focus on your project:

// Import the variable into the file
const server = require('server');

// All of the arguments are optional
server(options, fn1, fn2, fn3, ...);

You can also learn Node.js development by following the tutorials.

Getting started

There's a getting started tutorial for beginners. If you know your way around:

npm install server

Then create some demo code in your index.js:

// Import the library
const server = require('server');

// Answers to any request
server(ctx => 'Hello world');

Run it from the terminal:

node .

And open your browser on localhost:3000 to see it in action.

Basic usage

Some of the components are the main function on itself, router and reply. The main function accepts first an optional object for the options, and then as many middleware or arrays of middleware as wanted:

const server = require('server');

server({ port: 3000 }, ctx => 'Hello 世界');

To use the router and reply extract their methods as needed:

const server = require('server');
const { get, post } = server.router;
const { render, json } = server.reply;

server([
  get('/', ctx => render('index.hbs')),
  post('/', ctx => json(ctx.data)),
  get(ctx => status(404))
]);

Then when you are splitting your files into different parts and don't have access to the global server you can import only the corresponding parts:

const { get, post } = require('server/router');
const { render, json } = require('server/reply');

Middleware

A middleware is plain function that will be called on each request. It receives a context object and returns a reply, a basic type or nothing. A couple of examples:

const setname = ctx => { ctx.req.user = 'Francisco'; };
const sendname = ctx => send(ctx.req.user);
server(setname, sendname);

They can be placed as server() arguments, combined into an array or imported/exported from other files:

server(
  ctx => send(ctx.req.user),
  [ ctx => console.log(ctx.data) ],
  require('./comments/router.js')
);

Then in ./comments/router.js:

const { get, post, put, del } = require('server/router');
const { json } = require('server/reply');

module.exports = [
  get('/',    ctx => { /* ... */ }),
  post('/',   ctx => { /* ... */ }),
  put('/:id', ctx => { /* ... */ }),
  del('/:id', ctx => { /* ... */ }),
];

The main difference between synchronous and asynchronous functions is that you use async keyword to then be able to use the keyword await within the function, avoiding callback hell. Some examples of middleware:

// Some simple logging
const mid = () => {
  console.log('Hello 世界');
};

// Asynchronous, find user with Mongoose (MongoDB)
const mid = async ctx => {
  ctx.user = await User.find({ name: 'Francisco' }).exec();
  console.log(ctx.user);
};

// Make sure that there is a user
const mid = ctx => {
  if (!ctx.user) {
    throw new Error('No user detected!');
  }
};

// Send some info to the browser
const mid = ctx => {
  return `Some info for ${ctx.user.name}`;
};

In this way you can await inside of your function. Server.js will also await to your middleware before proceeding to the next one:

server(async ctx => {
  await someAsyncOperation();
  console.log('I am first');
}, ctx => {
  console.log('I am second');
});

If you find an error in an async function you can throw it. It will be caught, a 500 error will be displayed to the user and the error will be logged:

const middle = async ctx => {
  if (!ctx.user) {
    throw new Error('No user :(');
  }
};
Avoid callback-based functions: error propagation is problematic and they have to be converted to promises. Strongly prefer an async/await workflow.

Express middleware

Server.js is using express as the underlying library (we <3 express!). You can import middleware designed for express with modern:

const server = require('server');

// Require it and initialize it with some options
const legacy = require('helmet')({ ... });

// Convert it to server.js middleware
const mid = server.utils.modern(legacy);

// Add it as you'd add a normal middleware
server(mid, ...);

Note: the { ... } represent the options for that middleware since many of express libraries follow the factory pattern.

To simplify it, we can also perform this operation inline:

const server = require('server');
const { modern } = server.utils;

server(
  modern(require('express-mid-1')({ ... })),
  modern(require('express-mid-2')({ ... })),
  // ...
);

Or just keep the whole middleware in a separated file/folder:

// index.js
const server = require('server');
const middleware = require('./middleware');
const routes = require('./routes');

server(middleware, routes);

Then in our middleware.js:

// middleware.js
const server = require('server');
const { modern } = server.utils;

module.exports = [
  modern(require('express-mid-1')({ /* ... */ })),
  modern(require('express-mid-2')({ /* ... */ }))
];

Routing

This is the concept of redirecting each request to our server to the right place. For instance, if the user requests our homepage / we want to render the homepage, but if they request an image gallery /gallery/67546 we want to render the gallery 67546.

For this we will be creating routes using server's routers. We can import it like this:

const server = require('server');
const { get, post } = server.router;

// OR

const { get, post } = require('server/router');

There are some other ways, but these are the recommended ones. Then we say the path of the request for the method that we want to listen to and a middleware:

const getHome = get('/', () => render('index.pug'));
const getGallery = get('/gallery/:id', async ctx => {
  const images = await db.find({ id: ctx.params.id }).exec();
  return render('gallery.pug', { images });
});

Let's put it all together to see how they work:

const server = require('server');
const { get, post } = server.router;

const getHome = get('/', () => render('index.pug'));
const getGallery = get('/gallery/:id', async ctx => {
  const images = await db.find({ id: ctx.params.id }).exec();
  return render('gallery.pug', { images });
});

server(getHome, getGallery);

We can also receive post, del, error, socket and other request types through the router. To see them all, visit the Router documentation:

Router Documentation

Advanced topics

There is a lot of basic to mid-difficulty documentation to do until we even get here. Just a quick note so far:

The main function returns a promise that will be fulfilled when the server is running and can be accessed. It will receive a more primitive context. So this is perfectly valid:

server(ctx => 'Hello world').then(ctx => {
  console.log(`Server launched on http://localhost:${ctx.options.port}/`);
});

Keep reading

List of all the topics: