sans-server

2.0.3 • Public • Published

npm module downloads Build status Coverage Status

sans-server

Write code for a server, without the server.

  • Make requests that are representative of HTTP requests to a function.
  • Get responses that are representative of HTTP responses.
  • Accepts connect middleware.
  • Easier to test your server code (using direct function calls).
  • Faster to test your server code (no HTTP required).
  • Can be wrapped by any server.
  • Logs and profiling grouped by request.

Example

The sans-server package is a tool for building a functional web server that is independent of networking. As a functional library, requests can be made to it and responses provided by it.

const SansServer = require('sans-server');
 
// create a server instance
const server = SansServer();
 
// add middleware to the server
server.use(function(req, res, next) {
    if (req.path === '/') {
        res.send('OK');
    } else {
        next();
    }
});
 
// make a request against the server
server.request({ path: '/' }, function(err, response) {
    console.log(response.statusCode);   // 200
    console.log(response.body);         // 'OK'
});
 
// make a request against the server
server.request({ path: '/foo' }, function(err, response) {
    console.log(response.body);         // 'Not Found'
    console.log(response.statusCode);   // 404
});
 
// make a request using a promise
server.request({ path: '/' })
    .then(function(response) {
        console.log(response.statusCode);   // 200
        console.log(response.body);         // 'OK'
    });

Table of Contents

SansServer constructor

Create a Sans Server instance that follows the specified configuration.

Signature SansServer ([ config ]) : SansServer

Methods

  • hook - Add a hook to each request.
  • hook.define - Define a custom hook.
  • hook.type - Get the primitive from a hook symbol.
  • request - Make a request.
  • use - Add a middleware to each request.

Static Methods

Parameters

Parameter Description Type Default
config An optional object defining specific behavior for the instance. To see what options are accepted see Config Options. Object See Config Options
Config Options
Option Description Type Default
logs A boolean that specifies whether the grouped logs should be output at the end of a request. boolean true
rejectable A value that specifies if request promises should be rejected or automatically caught. If set to false then requests will always return a valid response. boolean false
timeout The number of seconds to wait prior to request timeout. Set this value to zero to disable the timeout. number 30
useBuiltInHooks A boolean specifying whether built in hooks should run for each request. This includes request method validation and response transformation. If set to false the built in hooks can still be added manually. boolean true

Returns a Sans Server instance.

Example

const SansServer = require('sans-server');
const server = SansServer({
    logs: {
        duration: false,
        grouped: true,
        silent: false,
        timeDiff: true,
        timestamp: false,
        verbose: false
    },
    timeout: 30,
    useBuiltInHooks: true
});

SansServer#hook

Add a hook to each request. For an explanation on hooks see Hooks and Middleware.

Signature SansServer#hook (type, [ weight, ] hook [, hook... ]) : SansServer

Parameters

Parameter Description Type Default
type The type of hook to apply the hook function to. Out of the box this package has a request and response hook, but other packages or modules can add to the list of available hooks. string
weight The weight of the hook(s) being added. A lower number means the hook will run sooner and a higher number means it will run later. Negative numbers are allowed. number 0
hook A hook function. Naming the function will improve log readability. Any number of hook functions can be defined at once. function

Returns The current SansServer instance.

Example: Single Hook

const SansServer = require('sans-server');
const server = SansServer();
 
server.hook('request', function myHook(req, res, next) {
    // run some logic here...
    next();
});

Example: Multiple Hooks

const SansServer = require('sans-server');
const server = SansServer();
 
server.hook('request', hook1, hook2);
 
function hook1(req, res, next) {
    // run some logic here...
    next();
}
 
function hook2(req, res, next) {
    // run some logic here...
    next();
}

Example: Single Hook that Runs Earlier

const SansServer = require('sans-server');
const server = SansServer();
 
server.hook('request', -100, function myHook(req, res, next) {
    // run some logic here...
    next();
});

SansServer#hook.define

Define a unique hook type. Attempting to define a second hook with the same name will throw an error.

Signature SansServer#hook.define ( type ) : Symbol

Parameters

Parameter Description Type Default
type The hook type to define. string

Returns a symbol that must be used to execute all hook functions that have subscribed to this hook chain.

Example

const SansServer = require('sans-server');
const server = SansServer();
 
// define custom hook
const key = server.hook.define('my-hook');
 
// during the request you can execute your custom hook
server.use(function myHook(req, res, next) {
    req.hook.run(key, next);
});

SansServer#hook.type

Get the primitive from a hook symbol.

Signature SansServer#hook.type ( symbol ) : string

Parameters

Parameter Description Type Default
symbol The symbol to get the hook type for. Symbol

Returns the string that was used to create the symbol.

Example

const SansServer = require('sans-server');
const server = SansServer();
 
// define custom hook
const key = server.hook.define('my-hook');
 
const type = server.hook.type(key);     // 'my-hook'

SansServer#request

Make a request against the server and get back a Request instance.

Signature SansServer#request ([ request ,] [ callback ]) : Request

Emits request : Request

Parameters

Parameter Description Type Default
request A request configuration. If a string is used then the string is considered to be the path and all other request defaults will be applied. string object See Request Configuration
callback A function to call when the request has been completed. The callback receives any error as its first parameter. The response state is provided as the second parameter whether there was an error or not. function
Request Configuration
Option Description Type Default
body The body of the request. This can be any data type, generally a primitive or a plain object is recommended. If the body contains a form payload then it should follow the request body documentation. ''
headers The request headers. This needs to be an object with string keys mapped to string values. For example: { headers: { 'content-type', 'plain/text' } }. object {}
method The request method. Must be one of 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'. Case is not important. string 'GET'
query The query string parameters. If a string then it will be parsed. If an object then each key must be a string and each value must be either a string, true, or an array of strings. object string {}
path The path for the request. The path should not include the protocol, domain, or port information. The path may contain query parameters and those will be pushed into the request query object automatically. string ''
Request Body

If the body is an object then it can be used to represent one of several content types: application/json, application/x-www-form-urlencoded, or multipart/form-data.

The type that the object represents is determined by the Content-Type header. If the body is an object and the Content-Type header is either application/x-www-form-urlencoded or multipart/form-data then the object should be in form body format.

Form body format is used when the body represents a submitted form, whether via application/x-www-form-urlencoded or multipart/form-data.

For example, look at this HTML form:

<form>
    Full Name:
        <input name="fullName">
        
    Interests: 
        <input type="checkbox" name="interests[]" value="Computers"> Computers
        <input type="checkbox" name="interests[]" value="Outdoors"> Outdoors
        <input type="checkbox" name="interests[]" value="Sports"> Sports
    
    Profile Picture:
        <input type="file" name="picture">
</form>

Assume that the form inputs had these values when the form was submitted:

  • fullName - 'Bob Smith'
  • interests - Both Computers and Sports where checked
  • picture - a file was selected

Then the request would look like this:

const body = {
    fullName: [
        {
            headers: {},
            content: "Bob Smith"
        }
    ],
    interests: [
        {
            headers: {},
            content: "Computers"
        },
        {
            headers: {},
            content: "Sports"
        }
    ],
    picture: [
        {
            headers: {},
            content: "dGhpcyBpcyBhIHBpY3R1cmUgZmlsZQ=="
        }
    ]
};
 
const sansServer = SansServer();
sansServer.request({ body: body });

Optionally the headers property for each form input can be omitted, but the content property is always required and must be a string.

Returns a Request instance.

Callback Example

const SansServer = require('sans-server');
const server = SansServer();
 
server.request('/get/some/path', function(err, res) {
    if (err) console.error(err.stack);
    console.log(res.body);        // the response body
    console.log(res.cookies);     // an object with the set cookies
    console.log(res.headers);     // an object with the set headers
    console.log(res.rawHeaders);  // an array of all headers and cookies serialized
    console.log(res.statusCode);  // the response status code
});

Promise Example

const SansServer = require('sans-server');
const server = SansServer();
 
server.request('/get/some/path')
    .then(function(res) {
        console.log(res.body);        // the response body
        console.log(res.cookies);     // an object with the set cookies
        console.log(res.headers);     // an object with the set headers
        console.log(res.rawHeaders);  // an array of all headers and cookies serialized
        console.log(res.statusCode);  // the response status code
    })
    .catch(function(err) {
        console.error(err.stack);
    });

Example with Config Object

const SansServer = require('sans-server');
const server = SansServer();
 
server.request({ method: 'POST', path: '/some/path', body: {}, headers: { Accept: 'application/json' } })
    .then(function(res) {
        // do something ...
    });

Example of Event Listening

const SansServer = require('sans-server');
const server = SansServer();
 
const req = server.request('/some/path')
    .on('log', function(event) { /* ... */ })
    .on('error', function(err) { /* ... */ })
    .on('response', function(res) { /* ... */ })
    .then(function(res) {
        // all hooks and middlewares have run their course
    });

Example with Query Parameters

const SansServer = require('sans-server');
const server = SansServer();
 
// these two requests are equivalent:
server.request('?name=Bob%20Smith&interests=Computers&interests=Sports&happy&extra=');
server.request({
   query: {
       name: 'Bob Smith',
       interests: ['Computers', 'Sports'],
       happy: true,
       extra: ''
   }
});

SansServer#use

Add a middleware hook to each request. This works the same as connect middleware and is equivalent to calling sansServer.hook('request', 0, myMiddlewareFunction). For an explanation on hooks see Hooks and Middleware.

Signature SansServer#use (middleware [, middleware... ]) : SansServer

Parameters

Option Description Type Default
middleware A middleware function. Naming the function will improve log readability. Any number of middleware functions can be defined at once. function

Returns the current Sans Server instance.

Example: Single Middleware

const SansServer = require('sans-server');
const server = SansServer();
 
server.use(function myMiddleware(req, res, next) {
    // run some logic here...
    next();
});

Example: Multiple Middleware

const SansServer = require('sans-server');
const server = SansServer();
 
server.use(first, second);
 
function first(req, res, next) {
    // run some logic here...
    next();
}
 
function second(req, res, next) {
    // run some logic here...
    next();
}

SansServer.hooks.validateMethod

A static method that is best used early in the request hooks. It validates that the HTTP method is one of (case insensitive) 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'.

This method is automatically used as a request hook with weight -100000 if the SansServer configuration options has useBuiltInHooks set to true. Otherwise you can add the hook manually like this:

Example

const SansServer = require('sans-server');
 
// create the Sans Server instance without using built in hooks
const sansServer = SansServer({ useBuildInHooks: false });
 
// add validate method request hook
sansServer.hook('request', -100000, SansServer.hooks.validateMethod);

SansServer.hooks.transformResponse

A static method that is best used late in the response hook.

This method will automatically set the Content-Type header if not set and it will transform the body into a string.

This is how the conversions are made. Content-Type is only modified if unset, unless otherwise specified below.

Body Type Sets Content-Type To Body Transformation
Error Always 'text/plain' 'Internal Server Error'
Buffer 'application/octet-stream' Convert to base64 encoded string.
Object 'application.json' Convert using JSON.stringify
string 'text/html' None
Anything else 'text/plain' Convert using String()

Example

const SansServer = require('sans-server');
 
// create the Sans Server instance without using built in hooks
const sansServer = SansServer({ useBuildInHooks: false });
 
// add validate method request hook
sansServer.hook('request', -100000, SansServer.hooks.transformResponse);

Request constructor

This constructor is invoked when calling SansServer#request and an instance of this constructor is returned by that function. This constructor cannot be invoked directly.

Because the request instance extends the Promise you can also use then and catch although the promise will never be rejected so you can skip using the catch function.

Extends EventEmitter Promise

Methods

  • catch - Catch any request processing errors. If the Sans Server rejectable is not set to true then this method is useless.
  • hook - Add a hook to the request.
  • hook.reverse - Run specified hook functions in reverse.
  • hook.run - Run specified hook functions in order.
  • log - Produce a request log event.
  • logger - Produce a logging function.
  • then - Assign a callback for the resolved promise.

Properties

  • body - Get or set the request body.
  • headers - Get or set the request headers.
  • id - Get the unique request ID.
  • method - Get or set the request method.
  • path - Get or set the request path.
  • query - Get or set the request query parameters.
  • res - Get the Response instance tied to this request.
  • server - Get a reference to the Sans Server instance that made this request.
  • url - Get the request URL, a combination of the path and query string parameters.

Events

Unless otherwise noted, each of these events provide the Response instance with the event.

  • error - Fires when an error occurs and provides the error as event data.
  • log - Fires when a message is logged and provides the following structure as it's event data: { action: string, category: string, details: object, message: string, timestamp: number }.
  • res-clear-header - Fired when a header is cleared.
  • res-complete - Fires after res-send event and after all response hooks have completed.
  • res-reset - Fires when the body, status code, headers, and cookies have all been reset to empty.
  • res-send - Fires when the Response#send function has been called.
  • res-set-body - Fired when the body has been modified.
  • res-set-cookie - Fired when a cookie is set or cleared.
  • res-set-header - Fired when a header is set.
  • res-set-status - Fired when the status code changes.
  • res-state-change - Fired when any of the response state has been modified.

Hooks

  • request - Runs when the request is initialized.

Request#catch

Add a rejection handler to the request promise.

Note, the request will never be rejected so any handler you define here will never be called. In the case of an error the request will still be resolved to a response with a 500 status code.

Signature Request#catch ( onRejected ) : Response

Parameters

Parameter Description Type Default
onRejected The function to call in case of promise rejection. function

Returns a Request instance.

Request#hook

Add a hook for just this request. Use SansServer#hook to add a hook for all requests.

Signature Request#hook ( type, [ weight, ] ...function ) : Request

Parameters

Parameter Description Type Default
type The hook for which the defined functions will be executed. string
weight How soon the hook should run in the hook sequence. Lower numbers run sooner and higher numbers run later. number 0
function The hook for which the defined functions will be executed. string

Returns the Request instance.

Example

const SansServer = require('sans-server');
const sansServer = SansServer();
 
const req = sansServer.request();
 
req.hook('response', 0, function(req, res, next) {
    req.log("I'm in a hook that add's a hook. Crazy");
    
    req.hook('xyz', function(req, res, next) {
        // ... do stuff
        next();
    });
    
    next();
});

Request#hook.reverse

Run the specified set of hooks in reverse.

Signature Request#hook.reverse ( key [, next ] ) : Promise | undefined

Parameters

Parameter Description Type Default
key The key that will allow running of the hooks. symbol
next An optional function that will be called after executing all hooks. If an error occurs this function will receive that as its first parameter, otherwise it will not receive a parameter. function

Returns a Promise if the next function was not provided as a parameter.

Example

const SansServer = require('sans-server');
const sansServer = SansServer();
 
// define a custom hook
const key = sansServer.hook.define('custom-hook');
 
// define a hook function for the response hook
sansServer.hook('response', 0, function(req, res, next) {
    
    // run the custom-hook hooks in reverse before calling next hook
    req.hook.reverse(key, next);
});

Request#hook.run

Run the specified set of hooks in order.

Signature Request#hook.run ( key [, next ] ) : Promise | undefined

Parameters

Parameter Description Type Default
key The key that will allow running of the hooks. symbol
next An optional function that will be called after executing all hooks. If an error occurs this function will receive that as its first parameter, otherwise it will not receive a parameter. function

Returns a Promise if the next function was not provided as a parameter.

Example

const SansServer = require('sans-server');
const sansServer = SansServer();
 
// define a custom hook
const key = sansServer.hook.define('custom-hook');
 
// define a hook function for the response hook
sansServer.hook('response', 0, function(req, res, next) {
    
    // run the custom-hook hooks in reverse before calling next hook
    req.hook.run(key, next);
});

Request#log

Produce a log event while processing a request.

Signature Request#log ([ type, ] message [, details ]) : Request

Parameters

Parameter Description Type Default
type A category that can be defined to help identify grouped log messages. string 'log'
message The log message. string object
details A string or object that only displays when logging is set to verbose. string object {}

Returns the Request instance.

Example

const SansServer = require('sans-server');
const server = SansServer();
 
server.use(function myMiddleware(req, res, next) {
    const start = new Date();
    req.log('myMiddleware', 'running', { start: start.toISOString() });
    res.log('myMiddleware', 'still running');
});

Request#logger

Produce a logging function. The returned function can be called to produce standardized log events. This function is used to produce the log function for the both the Request and Response.

Signature Request#logger (category, type, [ returnValue ]) : Function

Parameters

Parameter Description Type Default
category A short string that describes the overarching code that is producing the log function. A good option for this value would be the name of the NodeJS package producing the log function. string
type A classification that describes the subset of code that is producing the log function. A good option for this value would be the name of the module of code that is producing the log function. string
returnValue A value to run when the log function is called. Ideal for property chaining. Any undefined

Returns the log producing function. The function can be called with any parameters and will format the logged data similar to the console.log function, but this function will have the advantage of logging with the Debug package as well as grouping logs for a single request.

Example

This example is trivial and a bit of a waste. It would generally make more sense to produce a log function for a complex middleware that implements it's own sans-server-middleware.

const SansServer = require('sans-server');
const server = SansServer();
 
server.use(function myMiddleware(req, res, next) {
    const log = req.logger('my-package-name', 'this-module-name');
    log('This is a number %d', 5);
});

Request#then

Add fulfillment or rejection handlers to the request promise.

Note, the request will never be rejected so it is a waste to provide an onRejected parameter. In the case of an error then request will be resolved to a response with a 500 status code.

Signature Request#catch ( onFulfilled [, onRejected ] ) : undefined

Parameters

Option Description Type Default
onFulfilled The function to call in case of promise resolution. The function will receive the response state as its input parameter. function
onRejected The function to call in case of promise rejection. (Any function you supply to this will never be called.) function

Returns a Promise.

Response constructor

This constructor is invoked when calling SansServer#request and an instance of this constructor is attached to the Request instance. This constructor cannot be invoked directly.

Methods

  • body - Set the response body.
  • clearCookie - Remove a cookie by setting it as expired.
  • clearHeader - Remove a response header.
  • cookie - Set a response cookie.
  • log - Produce a response log event.
  • redirect - Redirect to client to a new location.
  • reset - Reset the body, headers, cookies, and status code.
  • send - Send the response.
  • sendStatus - Send the response with a status code and status message.
  • set - Alias for Response#setHeader .
  • setHeader - Set a response header.
  • status - Set the response status code.

Properties

  • req : Request - Get the request object that is associated with this response object.
  • sent : boolean - Get whether the request has already been sent.
  • server : SansServer - Get the SansServer instance tied to this request.
  • state : object - Get the current response state. See response state for details.
  • statusCode : number - Get or set the current response status code.

Hooks

  • response - Runs in reverse when the response is sent. Lower weights will still run first, but two functions of equal weight will run in reverse order from when they were set.
Response State

The response state is an object that represents the response at the point in time it was requested. It can be acquired using the Response#state` getter.

This object has the following structure:

{
    body: *,
    cookies: Array.<{ name: string, options: object, serialized: string, value: string }>,
    headers: Object.<string,string>,
    rawHeaders: Array.<string>,
    statusCode: number
}

Response#body

Set the response body.

Signature Response#body ( value ) : Response

Parameters

Parameter Description Type Default
value The value to set the body to. This value can be set to anything but once all response hooks have run it will be converted to either a string, a Buffer, or a plain object. any

Returns the Response instance.

Emits res-set-body res-state-change

Response#clearCookie

Remove a cookie by setting it as expired.

Signature Response#clearCookie ( name [, options ] ) : Response

Parameters

Parameter Description Type Default
name The name of the cookie to clear string
options The cookie options. You will need to match the domain and path of the client to clear the cookie. object {}

Returns the Response instance.

Emits res-set-cookie res-state-change

Response#clearHeader

Remove a response header.

Signature Response#clearHeader ( name ) : Response

Parameters

Parameter Description Type Default
name The name of the header to clear string

Returns the Response instance.

Emits res-clear-header res-state-change

Response#cookie

Set a response cookie.

Signature Response#cookie ( name, value [, options ] ) : Response

Parameters

Parameter Description Type Default
name The name of the cookie to set string
value The value of the cookie to set. string
options The cookie options that will be passed to the cookie package. object {}

Returns the Response instance.

Emits res-set-cookie res-state-change

Response#log

Produce a response log event.

Signature Response#log ( [ type, ], message [, details ] ) : Response

Parameters

Parameter Description Type Default
type The logged event's category. string 'log'
message A string message to log. string
details A detailed object that contains information that contains information relative to the logged message. This object could be used by log event handlers that want to parse the log data. object {}

Returns the Response instance.

Emits log

Response#redirect

Redirect to client to a new location.

Signature Response#redirect ( url ) : Response

Parameters

Parameter Description Type Default
url The endpoint to redirect the client to. string

Returns the Response instance.

Emits res-set-status res-set-header res-send res-state-change

Response#reset

Reset the body, headers, cookies, and status code.

Signature Response#reset ( ) : Response

Parameters None

Returns the Response instance.

Emits response-reset res-state-change

Response#send

Send the response.

Signature Response#send ( [ body ] ) : Response

Parameters

Parameter Description Type Default
body Optionally set the body during send. any

Returns the Response instance.

Emits res-send res-state-change res-complete error

Response#sendStatus

Send the response with a status code and status message.

Signature Response#sendStatus ( code ) : Response

Parameters

Parameter Description Type Default
code The status code to use to set the status and body. number

Returns the Response instance.

Emits res-set-status res-set-header res-set-body res-send res-complete res-state-change error

Response#set

Alias for Response#setHeader.

Response#setHeader

Set a response header.

Signature Response#setHeader ( name, value ) : Response

Parameters

Parameter Description Type Default
name The name of the header to set. string
value The value of the header to set. string

Returns the Response instance.

Emits res-set-header res-state-change

Response#status

Set the response status code.

Signature Response#status ( code ) : Response

Parameters

Parameter Description Type Default
code The status code to set. number

Returns the Response instance.

Emits res-set-status res-state-change

Hooks and Middleware

A hook defines the logic that each request passes through to determine its result. Middleware is a type of hook that runs with the incoming request. Technically you could do anything with the middleware hooks, but it may require rewriting or extending existing functions. To ease development Sans Server enables the use of additional hooks and the ability to create new hooks.

There are two types of hooks:

  1. Standard - these hooks are called when there are no errors. They receive three parameters: 1) the Request, 2) the Response, and 3) the next function.

    function myHook(req, res, next) {
        // do some logic
        next();
    }
  2. Error Handling - these hooks are only called when there are errors. They receive four parameters: 1) the Error, 2) the Request, 3) the Response, and 4) the next function.

    function myErrorHook(req, res, next) {
        // do some logic
        next();
    }

When you define a hook the number of parameters in your hook function is used to determine if it is for handling errors or not.

Next

Hooks commonly run as a chain of functions, one runs, followed by another, and so on. The next function is the mechanism used for continuing on to the next hook in line. By calling next() within your hook you are signifying that the hook is done processing.

If during processing an error occurs you have two options: 1) throw the error (synchronous only) or 2) pass the error to the next function (synchronous or asynchronous).

function myHook(req, res, next) {
    // do some logic
    throw Error('Oh no! An error.');
}
function myHook(req, res, next) {
    // do some logic
    next(Error('Oh no! An error.'));
}

Hook Flow

If you have a hook that produces an error then all non-error handling hooks will be skipped until an error handling hook is found.

const SansServer = require('sans-server');
const sansServer = SansServer();
 
sansServer.hook('request', function(req, res, next) {
    console.log('1');
    next(Error('An Error'));
});
 
sansServer.hook('request', function(req, res, next) {
    console.log('2');
    next();
});
 
sansServer.hook('request', function(req, res, next) {
    console.log('3');
    next();
});
 
sansServer.hook('request', function(req, res, next) {
    console.log('4');
    next();
});
 
/*
Console Output:
1
3
4
 */

Routing

Routing is not built into the core of sans-server, but you can add it with sans-server-router.

Versions

Current Tags

  • Version
    Downloads (Last 7 Days)
    • Tag
  • 1.5.2
    78
    • version-1
  • 2.0.3
    6
    • latest

Version History

Package Sidebar

Install

npm i sans-server

Weekly Downloads

84

Version

2.0.3

License

Apache-2.0

Last publish

Collaborators

  • gi60s