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:
- requestData
- renderFunc
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:
- requestData
- err
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];