blocks
Node.js API framework. You have a
request
and need to produce aresponse
.
Features
Validate input - ajv
Pass request data (headers, body, query parameters, URL parameters) through custom JSON Schemas defined for each route. Make sure no unwanted data gets in, de-clutter the route logic and make the API behave more consistent.
If validation fails, an automatic409 Conflict
response will be sent to the client.
Check permissions
Simple function outside of main route logic.
If it returns false, an automatic403 Forbidden
response will be sent to the client.
Async support
Route actions, middleware and plugins have
async/await
support.
Query string parsing - qs
Parsed data will be available on the
request.ctx
object.
Cross-origin resource sharing
Using
cors
Install
npm install @leeruniek/blocks
Basic example
src/index.js
const http = require("http")
const path = require("path")
const { block } = require("@leeruniek/blocks")
block({
// settings: {
// METRICS: true,
// METRICS_NAMESPACE: "blocks",
// METRICS_WITH_DEFAULT: false,
// PORT: 8080,
// MICRO_VERSION: pkg.version,
// ENV: "development",
// CORS_ORIGIN: null,
// CORS_METHODS: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
// },
// where plugins and routes are
folders: path.resolve("./src"),
// RegExp to match plugin files
// plugins: /\.plugins\.js$/,
// RegExp to match routes files
// routes: /\.routes\.js$/,
}).then(({ Plugins: { Config }, middlewarePipeline }) =>
http
.createServer(middlewarePipeline)
.listen(Config.get("PORT"), "localhost", () => {
console.log(`### Started server on port ${Config.get("PORT")}`)
})
)
Routes
Default routes
Status check
GET: /ping
{
"ping": "pong",
"aliveFor": {
"days": 2, "hours": 1, "minutes": 47, "seconds": 46
},
"version": "0.5.6", // package.json version
}
Prometheus data
GET: /metrics
If server started with METRICS: true
.
Add route
src/something.route.js
module.exports = {
method: "GET",
path: "/something",
// Data is valid then continue to permissionn check, otherwise return 409
schema: require("./something.schema"),
// Is allowed then continue to action, otherwise return 403
isAllowed: (/* plugins */) => async ({ method, ctx }) => {
console.log(`${method}:${ctx.pathname} - isAllowed`)
return true
},
// Returned value will be set in `res` body
action: (/* plugins */) => async (/* req */) => {
return {
message: "This is something else!"
}
},
}
src/something.schema.js
module.exports = {
type: "object",
properties: {
headers: {
type: "object",
},
params: {
type: "object",
additionalProperties: false,
},
query: {
type: "object",
additionalProperties: false,
},
body: {
type: "object",
additionalProperties: false,
},
},
}
See ajv
and JSON Schema docs for more on data validation.
Plugins
Default plugins
Config
Getter over the settings object when instantiating blocks
. Accessible in route's isAllowed
and action
methods and when defining custom middleware.
While you can use process.env
to access CI variables globally, use this opportunity to write a few words about each.
src/index.js
blocks({
settings: {
// (CI) Key to verify incoming json-web-tokens. See `jwt.middle.js` for details.
JWT_SECRET: process.env.JWT_SECRET ?? "CHANGE ME!"
...
},
...
})
src/middleware/req__jwt.js
module.exports = ({ Config }) => (req, res, next) => {
...
jwt.verify(req.headers.authorization, Config.get("JWT_SECRET"))
...
}
Add plugin
A plugin consists of a constructor function and a list of other plugins that is dependent on.
Whatever the create
function returns will be considered as the plugin's content and is what will be exposed to the routes, middleware and other plugins.
src/plugins/database.plugin.js
const Sequelize = require("sequelize")
module.exports = {
// Array of plugins to wait for before running `create`
depend: ["Config"],
/**
* Constructor function, whatever returns will be considered as the plugin's
* content and is what will be exposed to the routes, middleware and other
* plugins.
*
* @param {Object} props Initial settings object passed to the `blocks`
* constructor
*
* @returns {any|Promise<any>} Plugin content. Will resolve before continuing.
*/
create: (/* props */) => Config => {
console.log("Checking DB connection")
// Database connection, model loading etc
...
return {
Todos: ...,
Comments: ...,
}
}
}
Develop
git clone git@github.com:leeruniek/blocks.git && \
cd blocks && \
npm run setup
# run tests (any `*.test.js`) once
npm test
# watch `src` folder for changes and run test automatically
npm run tdd
Changelog
History of all changes in CHANGELOG.md
0.6 - March 2019
Add
- Diagrams and words describing how things work