Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

aldo

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aldo - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

index.d.ts

167

lib/application.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const dispatcher_1 = require("./dispatcher");
const http = require("http");
const util_1 = require("util");
const handlers_1 = require("./handlers");
const context_1 = require("./context");
const createDebugger = require("debug");
const aldo_http_1 = require("aldo-http");
const debug = createDebugger('aldo:application');
/**
* A global facade to manage routes, error handlers, dispatching, etc...
*
*/
class Application {
constructor() {
this._routes = [];
this._preHandlers = [];
this._postHandlers = [];
this._dispatcher = new dispatcher_1.default(_respond);
this._context = Object.create(null);
this._handlers = [];
this._errorHandlers = [];
this._context = new context_1.default();
}
/**
* Add before route handler
* Use request handlers
*
* @param fns
*/
pre(...fns) {
use(...fns) {
for (let fn of fns) {
this._preHandlers.push(_ensureFunction(fn));
debug(`use pre handler: ${fn.name || '<anonymous>'}`);
this._handlers.push(_ensureFunction(fn));
debug(`use handler: ${fn.name || '<anonymous>'}`);
}

@@ -31,21 +31,9 @@ return this;

/**
* Add after route handler
* Use error handlers
*
* @param fns
*/
post(...fns) {
for (let fn of fns) {
this._postHandlers.push(_ensureFunction(fn));
debug(`use post handler: ${fn.name || '<anonymous>'}`);
}
return this;
}
/**
* Add an error handler
*
* @param fns
*/
catch(...fns) {
for (let fn of fns) {
this._dispatcher.onError(_ensureFunction(fn));
this._errorHandlers.push(_ensureFunction(fn));
debug(`use error handler: ${fn.name || '<anonymous>'}`);

@@ -56,52 +44,22 @@ }

/**
* Set the final request handler
*
* @param fn final request handler
* Return a request callback
*/
finally(fn) {
this._dispatcher.onFinished(_ensureFunction(fn));
debug(`use final handler: ${fn.name || '<anonymous>'}`);
return this;
}
/**
* Register router's routes
*
* @param routers
*/
use(...routers) {
for (let router of routers) {
this._routes.push(...router.routes());
debug('use routes');
}
return this;
}
/**
* Return a request handler callback
*/
callback() {
this._registerRoutes();
return (request, response) => {
debug(`dispatching: ${request.method} ${request.url}`);
this._dispatcher.dispatch(this.makeContext(request, response));
var handleError = handlers_1.compose(this._errorHandlers.length > 0 ? this._errorHandlers : [_report]);
var dispatch = handlers_1.compose(this._handlers);
return (req, res) => {
var ctx = this._context.from(req, res);
debug(`dispatching: ${req.method} ${req.url}`);
dispatch(ctx).catch((err) => {
// ensure `err` is an instance of `Error`
if (!(err instanceof Error)) {
err = new TypeError(util_1.format('non-error thrown: %j', err));
}
// set the error
ctx.error = err;
return handleError(ctx);
});
};
}
async start(arg, options = {}) {
var server = this._server = aldo_http_1.createServer(options, this.callback());
if (typeof arg === 'number')
arg = { port: arg };
// listen
await server.start(arg);
debug(`app started with %o`, arg);
return server;
}
/**
* Stop listening for requests
*/
async stop() {
var server = this._server;
await server.stop();
debug(`app stopped`);
return server;
}
/**
* Extend the app context by adding per instance property

@@ -113,17 +71,4 @@ *

bind(prop, fn) {
_ensureFunction(fn);
var field = `_${prop}`;
Reflect.defineProperty(this._context, prop, {
configurable: true,
enumerable: true,
get() {
if (this[field] === undefined) {
// private property
Reflect.defineProperty(this, field, {
value: fn(this)
});
}
return this[field];
}
});
this._context.bind(prop, _ensureFunction(fn));
debug(`set a private context attribute: ${prop}`);
return this;

@@ -138,6 +83,4 @@ }

set(prop, value) {
if (typeof value === 'function') {
return this.bind(prop, value);
}
this._context[prop] = value;
this._context.set(prop, value);
debug(`set a shared context attribute: ${prop}`);
return this;

@@ -151,3 +94,3 @@ }

get(prop) {
return this._context[prop];
return this._context.get(prop);
}

@@ -160,34 +103,12 @@ /**

has(prop) {
return prop in this._context;
return this._context.has(prop);
}
/**
* Create a new copy of the context object
* Shorthand for:
*
* @param request
* @param response
* http.createServer(app.callback()).listen(...args)
*/
makeContext(request, response) {
var ctx = Object.create(this._context);
ctx.response = response;
ctx.request = request;
ctx.params = {};
ctx.app = this;
return ctx;
listen() {
return http.createServer(this.callback()).listen(...arguments);
}
/**
* Construct the routes tree
*
* @private
*/
_registerRoutes() {
for (let route of this._routes) {
for (let [method, fns] of route.handlers()) {
fns = [...this._preHandlers, ...fns, ...this._postHandlers];
this._dispatcher.register(method, route.path, fns);
}
// TODO register named routes
}
// reset the handlers
this._routes = [];
}
}

@@ -204,6 +125,6 @@ exports.default = Application;

return arg;
throw new TypeError(`Function expected but ${typeof arg} given.`);
throw new TypeError(`Function expected but got ${typeof arg}.`);
}
/**
* Send the response
* Send the error response
*

@@ -213,4 +134,6 @@ * @param ctx

*/
function _respond(ctx) {
ctx.response.send();
function _report({ error, res }) {
res.statusCode = error.status || 500;
res.end(error.message);
console.error(error);
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var route_1 = require("./route");
exports.Route = route_1.default;
var router_1 = require("./router");
exports.Router = router_1.default;
var application_1 = require("./application");
exports.Application = application_1.default;
{
"name": "aldo",
"version": "0.2.0",
"description": "Fast web framework for Node.js",
"version": "0.3.0",
"description": "Fast micro framework for Node.js 8+",
"main": "lib/index.js",
"scripts": {
"build": "tsc",
"prepublishOnly": "tsc && npm test",
"prepublishOnly": "npm run build",
"test": "mocha -R dot -r ts-node/register \"test/**/*.ts\""

@@ -20,6 +20,6 @@ },

"keywords": [
"fast",
"http",
"web",
"framework",
"http",
"fast"
"framework"
],

@@ -33,3 +33,5 @@ "author": "absolux",

"devDependencies": {
"@types/debug": "0.0.30",
"@types/mocha": "^2.2.48",
"@types/node": "^8.9.5",
"mocha": "^5.0.1",

@@ -40,8 +42,4 @@ "ts-node": "^4.1.0",

"dependencies": {
"@types/debug": "0.0.30",
"@types/node": "^8.9.2",
"aldo-http": "^0.2.0",
"debug": "^3.1.0",
"find-my-way": "^1.10.1"
"debug": "^3.1.0"
}
}
> This project is under heavy development, and the API may change frequently.
> This project is under heavy development, and the API is unstable and may change frequently.
`Aldo` is yet another framework to build Node.js web applications.
It uses the best parts of `Koa` and `Express` to provide a fast engine for your web projects.
`Aldo` is a micro framework that helps you quickly write simple yet powerful web applications and APIs for Node.js 8+.
It is super fast and has very little code.
In fact, you can read and understand its source code in only an afternoon!
## Installation
```bash
npm add aldo
```
At its core, `Aldo` is a dispatcher that invokes a serial callback stack to handle the incoming HTTP request. That’s it.
## Testing
```bash
npm test
```
## Hello world!
```js
// import the needed parts
const { Application, Router } = require('aldo')
const { Application } = require('aldo')
const router = new Router()
const app = new Application()
// we define a `hello world` route
router.get('/', () => 'Hello world!')
// add the first handler
app.use(({ res }) => {
res.setHeader('Content-Type', 'text/plain')
res.end('Hello world!')
})
// we add the router to be used by our application
app.use(router)
// we serve the application,
// which creates and uses an internal HTTP server
app.start(3000)
// create a HTTP server to serve the application
app.listen(3000)
```
## Request Flow
The request handling logic is similar to the `try..catch..finally` JavaScript block.
In other words, the application will try to call the route handlers one by one to the final handler.
If an error occurs, it will be handled by the error handlers before reaching the final handler which will terminate and send the response to the client.
## Handlers (aka middlewares)
Unlike the other web frameworks, `Aldo` uses handlers that take only **one** argument "the [*context*](#context)", which is a literal object holding everything you need to handle the incoming HTTP request.
You can use `.pre`, `.post`, `.use`, `.catch` and `.finally` application's methods to control the flow of request handling.
When the current handler has finished its job, the [context](#context) object is passed automatically to the next handler, and so on, till the last one.
```js
const app = new Application()
To break this chain, just return `false` or `Promise<false>`.
// 1. The `try` block
```ts
// Handler function signature
declare type Handler = (ctx: Context) => any;
```
// attach one or more global handlers before the route
// useful to configure global services like session, cache ...etc
app.pre(...handlers)
You may be wondering, where is the `next` argument ?
`Aldo` doesn't provide it, only the [context](#context) is given.
The `next` function is invoked internally after the current handler has finished.
// use one or many routers with `.use`
app.use(...routers)
// ... etc
```js
// the `next` function is called immediately after a sync handler
app.use(({ req: { url, method } }) => {
console.log(`incoming request: ${method} ${url}`)
})
// attach global handlers to be executed after the route handlers
// like saving a cached version, persisting session data, setting more headers ...etc
app.post(...handlers)
// the `next` function is awaiting async functions
app.use(async (ctx) => {
await fetch('some data from the DB')
})
// the `next` function is also awaiting sync functions returning promises
app.use((ctx) => {
return fetch('some data from the DB')
})
```
// 2. The `catch` block
### Request handlers
Request handlers are invoked for every incoming HTTP request.
You may add handlers with the application instance’s `.use(...fns)` method.
// attaching error handlers is done as below
app.catch(...handlers)
```js
// will attach one or more global handlers
app.use(...handlers)
```
### Error handlers
You may attach error handlers as many as needed with `.catch(...fns)`.
// 3. The `finally` block
// at last, only one final handler is used
// the default one is simply sending the response
app.finally(finalHandler)
```js
app.catch(...handlers)
```
> Since each method controls a processing step, the order doesn't matter any more.
> We can define routes before or after the final handler, it won't create any issue, since the routes are only compiled during the application launch.
When an error is thrown, during the request handling, will be attached to the [context](#context) object and passed to each handler one by one, unless you return a `false` which will stop the error handling sequence.
Unlike the other web frameworks, each `handler` takes only **one** argument: the **`context`**. This object holds everything you need to handle the incoming HTTP request.
Handlers may return *void* or a promise for asynchronous operations.
## Context
The context object is not a proxy to the request and response properties, it's a simple plain object with only 2 mandatory properties: `req`, `res`, which are the `IncomingMessage` and the `ServerResponse` HTTP native objects.
So, when the current handler finished its job, the *context* object is passed automatically to the next handler, and so on, till the final handler which terminates and ends the response.
To break the chain, throw an error, to get the first error handler called instead of the next middleware.
The error handlers receive the same object, but with an additonal `error` property.
```ts
// Handler function signature
declare type Handler = (ctx: Context) => any
declare type Literal = { [x: string]: any; };
declare interface Context extends Literal {
req: IncomingMessage;
res: ServerResponse;
error?: any;
}
```
## Context
The context object is not a proxy to the request and response properties, it's a simple plain object with only 4 mandatory properties `app`, `request`, `response` and route `params`.
Even the error handlers have the same signature, but with an additonal `error` property.
### `.set(key, value)`
To extend the request context, and add shared properties, like a DB connection, or a global logger, you may use `.set()`
```ts
declare type Literal = { [x: string]: any }
```js
const mongoose = require('mongoose')
declare interface Context extends Literal {
response: Response; // Response object provided by the package `aldo-http`
request: Request; // Request object provided by the package `aldo-http`
app: Application; // Application instance
params: Literal; // Route parameters
error?: any; // The error
}
await mongoose.connect('mongodb://localhost/test')
app.set('db', mongoose)
```
To extend the request context, and add more properties, you can use `app.set(key, value)` or `app.bind(key, factory)`
### `.bind(key, fn)`
To set a per request private properties, you may use `.bind()`. This method takes a field name, and a function to be used as a `lazy` getter of the field value.
```js
// set a global value to be available for all requests
app.set('mongo', dbConnection)
// set a per request property using a function to lazily get the value
// This time, each context instance has a distinct `session` property
app.bind('session', () => new Session(options))
```
`app.has(key)` and `app.get(key)` are also available to check the existence of a certain field, or to get a previously defined property.
This method is very useful, since it allows you to lazily (only when needed) attach a per request property into the context without adding a dedicated handler.
## Router
Each `router` instance control an erea in the application, it acts like a route namespace.
You can use as many routers as you need. For example, a router to manage authentication, another for the API, a private router for admin routing, and so on.
### `.has(key)`
You may use it to check the existence of a certain field
> The order of defining routes is not important any more. Thanks to [find-my-way](https://npmjs.com/find-my-way) witch is a [radix tree](https://en.wikipedia.org/wiki/Radix_tree).
### `.get(key)`
You may use it to get a previously defined field value.
```js
const { Router } = require('aldo')
const { Users } = require('./controllers')
## Contributing
You have a lot of ways to help the project grow.
// Let's create an admin area router
// `/admin` is a URL prefix for all routes
const router = new Router('/admin')
- Report an [issue](https://github.com/aldojs/aldo/issues)
- Propose a [feature](https://github.com/aldojs/aldo/issues)
- Send a [pull request](https://github.com/aldojs/aldo/pulls)
- Star the project on [GitHub](https://github.com/aldojs/aldo)
- Tell about project around your community
// define a single handler for admin home page for the GET method
router.get('/', Users.home)
All contributions are appreciated, even the smallest. Thank you!
// we can define multiple handlers per HTTP method for the same route
router
.route('/Users')
.get(Users.show)
.delete(Users.delete)
.post(Users.validate, Users.add)
.put(Users.validate, Users.modify)
```
## License
[MIT License](https://opensource.org/licenses/MIT)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc