escobar

3.0.5 • Public • Published

Escobar

Node.js micro framework for REST API or any other applications based on HTTP server.

Application example with recommended architecture is here

Requirements

Node.js that supports async/await. (version 7.6 or higher has native support)

Features

  • 0 dependencies
  • Async/await
  • Fast
  • Simple
  • Flexible
  • Light

Navigation

Installation

npm i --save escobar

Quick start example

const EscobarServer = require('escobar');
const server = new EscobarServer();

server.host = '127.0.0.1';
server.port = 8080;
server.loadRoutes(__dirname + '/routes'); // Load routes from folder
server.startServer();

Documentation with examples

Escobar server

Settings

.host - Http server binding host. (Default: '0.0.0.0')

.port - Http server binding port. (Default: 3000)

.httpServer - Node.js http.Server (Available after .startServer() exec`)

server.httpServer.timeout = 30000; // Set timeout to 30 sec.

.routes - Object with routes functions. (Default: {})

{
    '/': [function],
    '/some/endpoint': [function]
}

.useJsonParser - Parse request body with json, when Content-Type is 'application/json'. (Default: true)

.useMultipartParser - Parse request body (files and data), when Content-Type is 'multipart/form-data'. (Default: true)

.useUrlencodedParser - Parse request body data, when Content-Type is 'application/x-www-form-urlencoded'. (Default: true)

Events

All events functions must be async or return Promise.

Event: 'request'

Fires when we got new request.

Arguments:

server.on('request', async (requestData) => {
   const res = requestData._response;
   const req = requestData._request;

   // Set response headers (default)
   res.setHeader('Content-Type', 'application/json; charset=utf-8');

   // Headers for cross domain requests
   if (req.method == 'OPTIONS') {
       res.setHeader('Allow', 'GET,POST,PUT,DELETE,OPTIONS');
   }
   res.setHeader('Access-Control-Allow-Origin', '*');
   res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');


   // Or some stuff
   // req.on('data', (chunk) => { console.log('request body chunk: ' + chunk) });
   // req.on('end', () => {console.log("client request is complete. Let's send him response!")});

   // If you set server.useJsonParser or server.useMultipartParser or server.useUrlencodedParser to false
   // You can use your tools to parse this data

   return true;
});

Event: 'before_endpoint'

Fires before routing function will be executed. If it return true - routing function will be executed. If it return false - routing function will NOT be executed.

Arguments:

server.on('before_endpoint', async (requestData) => {
    // Get sessionId
    requestData._sessionId =
        requestData._request.headers.sessionid
        || requestData.$_GET.sessionId
        || requestData._http.getCookie(__sessionCookieName)
        || requestData._sessionId;
        
    // Some function that check access
    requestData._user = await auth(requestData);
    
});

Event: 'exec_route'

If this event is handled, you need to rewrite default execution. renderFunc - route function.

Arguments:

Default route execution is simply:

requestData._clientResponse = await renderFunc(requestData);

Custom execution example:

server.on('exec_route', async (requestData, renderFunc) => {
    const method = requestData._request.method;
    const funcToExec = renderFunc[method];

    if (funcToExec) {
        if (funcToExec.authOnly && !requestData._user) {
            requestData._clientResponse = __unauthorized(requestData, "You don't have access to this resource.");
            return false;
        }

        requestData._clientResponse = await funcToExec.exec(requestData);
    } else {
        requestData._clientResponse = __badRequest(requestData, `Method '${method}' is not supported for this endpoint`);
    }

    return true;
});

// Route index file
// ./routes/some/endpoint/__index.js
module.exports = {
    GET: require('./GET'),
    POST: require('./POST')
};

// Route logic file
// ./routes/some/endpoint/GET.js
module.exports = {
    authOnly: true,
    exec: async (requestData) => {
        // Return response string that will be delivered to the client
        return {
            status: 'OK',
            data: 'Hello world!'
        };
    }
};

Event: 'before_send_response'

Fires before we send response to client (response.end(requestData._clientResponse);).

Arguments:

server.on('before_send_response', async (requestData) => {
    // You can modify requestData._clientResponse here and it will be sent to client modified

    try {
        if (typeof requestData._clientResponse === 'object') {
            requestData._clientResponse = JSON.stringify(requestData._clientResponse);
        }
    } catch (e) {
        requestData._clientResponse = JSON.stringify({
            status: "FAIL",
            message: getErrorMsg(e)
        })
    }
});

Event: 'not_found'

Fires when we don't find any route for request. Example (https://example.com/endpoint/that/does/not/exists)

Arguments:

const endpointNotFound = JSON.stringify({
    status: "FAIL",
    message: "Endpoint not found"
});

server.on('not_found', async (requestData) => {
    // Don't exec server.onBeforeSendResponse
    // Because i don't want to waste CPU time for JSON.stringify every time
    requestData._execOnBeforeSendResponse = false;

    requestData._clientResponse = endpointNotFound;
    
    return true;
});

Event: 'error'

Fires when we got error.

Arguments:

const getErrorMsg = (e) => {
    let msg = 'Internal Server Error';
    if (typeof e == 'string') msg = e;
    if (typeof e == 'object') {
        if (e.msg) msg = e.msg;
        if (e.message) msg = e.message;
    }
    
    return msg;
};

server.on('error', async (requestData, err) => {
    requestData._http.setCode(500);
    
    requestData._clientResponse = {
        status: 'FAIL',
        message: getErrorMsg(err)
    };
    
    return true;
});

Events life cycle

  • await util executed: request
  • await util executed: before_endpoint
  • await util executed: exec_route
  • await util executed: before_send_response

Functions

.startServer() - Start server.

.loadRoutes(pathToFolder) - Load routes from folder. pathToFolder - Full path to folder that contains routes.

server.loadRoutes(__dirname + '/routesFolder');

How to use routes folder?

Here is example of files and folder structure:

routesFolder
    __main
        __index.js
    someFolder
        anotherFolder
            __index.js
    auth
        __index.js

__index.js - entry point for route.

__main folder - route for '/'.

Result routes:

{
    '/': [function],
    '/someFolder/anotherFolder': [function],
    'auth': [function]
}

requestData

requestData - is main object for manipulating your application.

requestData has a following properties by default for each request:

._request - Node.js http request from client.

._response - Node.js http response from server.

._route - Endpoint route (example: '/api/version'). (Default: false)

NOTE: If you define it inside .onRequest callback, routing will try to navigate using this url, instead of url from request.

._routeParams - See explanation below. (Default: [])

Example 1:
We have:
Route with endpoint /api/user
Request with URL /api/user/1337/remove

In this case requestData._routeParams will be [1337, 'remove'].


Example 2:
Route with endpoint /api/user
Request with URL /api/user

In this case requestData._routeParams will be empty array [].

._clientResponse - This will be sent to client. (Default: '')

._execOnBeforeSendResponse - Do we need to exec callback onBeforeSendResponse?. (Default: true)

._execRouting - Do we need to exec routing flow?. NOTE: Change this property available only inside onRequest callback. (Default: true)

._execRoute - Do we need to exec route or 'exec_route' event?. (Default: true)

._customResponse - If true, response.end(requestData._clientResponse); will not be executed in the end of request life cycle.

.$_DATA - Parsed data from request body. (Default: {})

.$_GET - Parsed data from query params. (Default: {})

.$_FILES - List of uploaded files (when Content-Type: multipart/form-data). (Default: {})

._http - Help functions. See list below.

  • .setCookie(name, value, options = false) - Set cookie.
requestData._http.setCookie('token', 'someTokenvalue', {
    domain: '.example.com',
    httponly: true,
    secure: true
});
  • .getCookie(name) - Get cookie. Returns false if cookie does't exists.
const token = requestData._http.getCookie('token');
  • .getCookies() - Get all cookies.
const cookies = requestData._http.getCookies();

/*
cookies = {
    token: 'someTokenvalue',
    cityId: 231
}
*/
  • .removeCookie(name) - Remove cookie.

  • .setCode(code) - Set status code and status message for response.

This function do following stuff:

requestData._response.statusCode = code;
requestData._response.statusMessage = http.STATUS_CODES[code];

Package Sidebar

Install

npm i escobar

Weekly Downloads

22

Version

3.0.5

License

MIT

Unpacked Size

39.6 kB

Total Files

10

Last publish

Collaborators

  • antonbarinov