larvitbase
v4.0.98
Published
http application base framework
Downloads
805
Readme
Micro web framework
Index
What is it?
A scaled down version of Express. It is as micro as micro can be, it only runs an array of middlewere functions, nothing more.
Why?
- More flexibility due to all functionality being in the middleware functions (no built-in "helpers", router, view system etc)
- In contrast to Express, it is possible to run a middleware before the router, or after the controller
- Better separations of concerns (routing, view system etc should not be a part of the base framework!)
- No dependencies in production (only development dependencies)
- Less is more
Installation
npm i larvitbase
Usage
In case you're building a full webb system, you probably want to go directly to larvitbase-www or if you're building an API, you might want to look at larvitbase-api. Both of these is larvitbase plus some middlewares to handle the basics of each scenario.
Minimal basic example
This will create a http server on port 8001 that will print "Hello world" whatever you send in.
const App = require('larvitbase');
let app = new App({
'httpOptions': 8001, // Listening port
'middlewares': [
function (req, res) {
res.end('Hello world');
}
]
});
app.start(function (err) {
if (err) throw err;
});
Routing
Routing is how to match a special URL to a specific piece of code or pieces of code.
There is no built in routing, but it is easy to make your own or use the more fully fledged larvitrouter.
Roll your own
Something like this:
const App = require('larvitbase');
let app;
function router(req, res, cb) {
if (req.url === '/') {
req.controller = controllerOne;
} else if (req.url === '/foo') {
req.controller = controllerTwo;
} else {
req.controller = notFound;
}
cb();
}
function runController(req, res, cb) {
req.controller(req, res, cb);
}
function controllerOne(req, res, cb) {
res.end('This is controllerOne! Hepp!');
}
function controllerTwo(req, res, cb) {
res.end('This is the second controller function! Hola!');
}
function notFound(req, res, cb) {
res.statusCode = 404;
res.end('The URL matched no controller. Ledsen i ögat. :(');
}
app = new App({
'httpOptions': 8001, // Listening port
'middlewares': [
router, // First run the router
runController // Then run the routed controller
]
});
app.start(function (err) {
if (err) throw err;
});
Test your application
From the path of your application, type:
node ./index.js
Then go to a browser and go to http://localhost:8001 and you should see "This is controllerOne! Hepp!". Test the URL:s /foo and /something as well and see what happends.
larvitrouter
For a bit larger application, it is often desireble with a more competent router. For this we have larvitrouter. It will resolve paths to controller files as well as template files and static files. See the documentation for an in depth guide. Here follows a small example:
First install it:
npm i larvitrouter
index.js
const Router = require('larvitrouter'),
router = new Router(),
App = require('larvitbase');
let app;
function runRouter(req, res, cb) {
router.resolve(req.url, function (err, result) {
req.routeResult = result; // Store the route result on the request so we can access it from elsewhere
cb(err);
});
}
function runController(req, res, cb) {
// A controller file was found!
if (req.routeResult.controllerFullPath) {
const controller = require(req.routeResult.controllerFullPath);
controller(req, res, cb);
// No controller file was found
} else {
res.statusCode = 404;
return res.end('Not found');
}
}
app = new App({
'httpOptions': 8001, // Listening port
'middlewares': [
runRouter,
runController
]
});
app.start(function (err) {
if (err) throw err;
});
controllers/foo.js
To make the URL /url work on our application, we create this file and save it like this:
'use strict';
exports = module.exports = function controllerFoo(req, res, cb) {
res.end('Foo custom page');
cb();
}
controllers/default.js
The default controller is a bit special. It will match both / and /default.
'use strict';
exports = module.exports = function controllerFoo(req, res, cb) {
res.end('Default page');
cb();
}
Templates
In this section we're going to implement EJS template engine.
First install dependencies:
npm i larvitbase ejs
Files
index.js
const ejs = require('ejs'),
App = require('larvitbase');
let app;
function controller(req, res, cb) {
// Set some different data depending on URL
res.data = {};
if (req.url === '/') {
res.data.someText = 'First page';
} else if (req.url === '/foo') {
res.data.someText = 'A sub page';
} else {
res.data.someText = 'No page';
}
cb();
}
function renderTemplate(req, res, cb) {
res.body = ejs.render('<html><body><h1><%= someText %></h1></body></html>', res.data);
res.end(res.body);
cb();
}
app = new App({
'httpOptions': 8001, // Listening port
'middlewares': [
controller,
renderTemplate
]
});
app.start(function (err) {
if (err) throw err;
});
Forms
To parse forms (and file uploads, url parameters and more) we can use larvitreqparser.
Install dependencies:
npm i larvitbase larvitreqparser
index.js
const ReqParser = require('larvitreqparser'),
reqParser = new ReqParser(),
App = require('larvitbase');
let app;
function controller(req, res, cb) {
res.body = '<html><body><form method="post">';
res.body += '<p>Write something: <input name="foo" /></p>';
if (req.formFields && req.formFields.foo) {
res.body += '<p>Something was written: "' + req.formFields.foo + '"</p>'
}
res.body += '<p><button type="submit">Click me</button></p>';
res.body += '</body></html>';
res.end(res.body);
cb();
}
app = new App({
'httpOptions': 8001, // Listening port
'middlewares': [
reqParser.parse.bind(reqParser),
controller
]
});
app.start(function (err) {
if (err) throw err;
});
Static Files
To feed static files, we are going to use a router to determine if the URL points to a static file, and then send to stream the file to the client.
Install dependencies:
npm i larvitbase larvitrouter send
index.js
const Router = require('larvitrouter'),
router = new Router({'staticsPath': __dirname + '/public'}), // This is the default, but we're explicit here
send = require('send'),
App = require('larvitbase');
let app;
function runRouter(req, res, cb) {
router.resolve(req.url, function (err, result) {
req.routeResult = result;
cb(err);
});
}
function controller(req, res, cb) {
// Static file found! Stream it to the client.
if (req.routeResult.staticFullPath) {
send(req, req.routeResult.staticFullPath).pipe(res);
// End the controller here, nothing more should be sent to the client
return cb();
}
// What to show if no static file is found:
res.body = '<html><body><h1>Show a static file:</h1>';
res.body += '<p><a href="foo.txt">foo.txt</a></p>';
res.body += '</body></html>';
res.end(res.body);
cb();
}
app = new App({
'httpOptions': 8001, // Listening port
'middlewares': [
runRouter,
controller
]
});
app.start(function (err) {
if (err) throw err;
});
public/foo.txt
Just save a text file with whatever content in hour applications path + public/foo.txt
Error handling
In case something goes wrong inside any of your middlewares and an error is returned, we need to handle that somehow. This is how:
const App = require('larvitbase');
let app;
app = new App({
'httpOptions': 8001, // Listening port
'middleware': [
function (req, res, cb) {
return cb(new Error('Something went wrong! :('))
}
]
});
// Handle errors in one of the middleweres during a request
app.on('error', function (err, req, res) {
res.statusCode = 500;
res.end('Internal server error: ' + err.message);
});
app.start(function (err) {
if (err) throw err;
});
Logging
The App takes a log option and it requires it to be an object with the following methods: silly(), debug(), verbose(), info(), warn() and error().
Historically we've used winston, but any object with the above methods will work fine. If no log instance is supplied, an instance of larvitutils Log will be used with level "info".
Log levels used:
- error - Fatal! Application should not continue to run at all
- warn - Important problem. Application might be able to continue, but this should be addressed to maintain stability.
- info - Important information. Probably not a problem, but information of high value to sysops.
- verbose - Nice-to-have information. Statistis about each request run time and such.
Do not use in production
- debug - Debug information. Further statistics and other debug info. Will flood your logs if used in production!
- silly - Silly amounts of information. Will flood your logs even if not in production, your terminal will explode.
Middleware functions
Middleware functions are compatible with Express, and follow the same principles:
Middleware functions can perform the following tasks:
- Execute any code.
- Make changes to the request and the response objects.
- End the request-response cycle.
- Call the next middleware function in the stack.
Larvit middlewares
- larvitreqparser - handle request data, forms (POST, PUT, DELETE) etc
- larvitsession - sessions to remember data between requests