@ogre.dev/framework
TypeScript icon, indicating that this package has built-in type declarations

1.0.6 • Public • Published

The Ogre Framework

license downloads npm version codecov

Fast and minimalist framework for Node.js written in TypeScript.

Ogres are like onions... They have layers.

import Ogre from '@ogre.dev/framework";
const onion = new Ogre();

onion.addLayer((context, next) => {
  const { response } = context;

  response.setBody('What are you doing in my swamp!?');
});

onion.listen(3000);

Installation

The framework is available through an npm registry:

npm install @ogre.dev/framework

Onions and layers

Unlike most frameworks where middleware functions are called sequentially, the Ogre framework uses a layer system where the first layers in the stack wrap around the following layers, much like onions or Russian dolls.

A Layer takes a context and a reference to the next layer as arguments:

const layer: Layer = (context, next) => {
  ...
};

Whenever a layer calls its next parameter, the request lifecycle enters the next layer in the stack. Once the execution of the next layer has finished, the initial layer can proceed with the rest of its code.

Executing the following:

import Ogre from '@ogre.dev/framework';

const onion = new Ogre();

onion
  .addLayer((context, next) => {
    console.log('entering layer 0');
    next();
    console.log('exiting layer 0');
  })
  .addLayer((context, next) => {
    console.log('entering layer 1');
    next();
    console.log('exiting layer 1');
  })
  .addLayer((context, next) => {
    console.log('entering layer 2');
    console.log('exiting layer 2');
  };

onion.listen(3000);

...will print:

entering layer 0
entering layer 1
entering layer 2
exiting layer 2
exiting layer 1
exiting layer 0

Layers's next method can only be called once. Subsequent calls are ignored.

Note that layers can be asynchronous. When in doubt, it is advised to await the execution of the next layer.

A typical use case would be to record the time it took to respond to the request:

import Ogre from '@ogre.dev/framework';

const onion = new Ogre();

onion
  .addLayer(async (context, next) => {
    const tic = new Date().getTime();

    await next();

    const toc = new Date().getTime();

    console.log(`execution took ${toc - tic}ms`);
  })
  .addLayer(async () => new Promise((resolve) => setTimeout(resolve, 1000));
  });

onion.listen(3000);
execution took 1002ms

Routing

Routing overview

The Ogre framework adopts a REST approach to routing with the extendable Resource class to model HTTP resources.

For instance, let's consider the path /users/123 which targets the users HTTP resource with a specific userId of 123.

Here is how you would define a User resource with a simple GET handler:

import { Resource } from '@ogre.dev/framework';

class UserResource extends Resource {
  path = '/users/{userId}';

  get = (context) => {
    const { request, response } = context;

    response.setBody({ userId: request.pathParameters.userId });
  };
}

export default new UserResource().toLayer();

Note that the userId path variable is accessible through context.request.pathParameters.

You can then import the resource into your app and use it as a layer:

import User from './resources/User';

...

onion.addLayer(User);

...

Paths

As we have seen in the previous section, resources revolve around their paths.

The path property can be either a string or directly a regular expression. The former may contain path variables surrounded by brackets (e.g. {userId}) that are made accessible through context.request.pathParameters inside the resource layers, or an asterisk (e.g. /users/*) to match all paths starting with the pattern preceding the symbol.

For instance /users/{userId}/* will match /users/123, /users/my-user, /users/123/this/is/an/example etc.

Best practice

The example shown in the routing overview section is intentionally simple and is only intended to help new users get started.

For more advanced applications with many resources, it is best advised to follow a modular approach with a project structure similar to the following:

main.ts
/resources
  /Users
    index.ts
    /controllers
      get.ts
      post.ts
      index.ts
// /resources/Users/index.ts

import { Resource } from '@ogre.dev/framework';
import { get, post } from './controllers';

class Users extends Resource {
  constructor() {
    super('/users');
    this.get = get;
    this.post = post;
  }
}

export default new Users().toLayer();
// /resources/Users/controllers/get.ts

import { Layer } from '@ogre.dev/framework';

const get: Layer = (context) => {
  const { response } = context;

  response.setBody([/* list of users */]);
};

export default get;
// /resources/Users/controllers/index.ts

export { default as get } from './get';
export { default as post } from './post';
// main.ts

import Users from './resources/Users';

...

onion.addLayer(Users);

...

Fallback handler

You may override the fallback method of the Resource class with a custom layer to handle cases where the request path matched the resource's but no handlers were found for the requested HTTP method.

Error handling

The Ogre framework tries to remain as little opinionated as possible, instead leaving the user to decide what works best for their particular needs.

By default no error handling is configured. In most cases however, common errors such as 404 - Not Found and 500 - Internal Server Error should be dealt with. This can be achieved with the following:

// main.ts

import Ogre from '@ogre.dev/framework';
import Users from './resources/Users';

const onion = new Ogre();

onion
  .addLayer(async (context, next) => {
    try {
      await next();
    } catch (error) {
      // handle 500 errors
      const { response } = context;

      response
        .setStatus(HttpStatusCode.INTERNAL_SERVER_ERROR)
        .setBody({ message: HttpStatusReason.INTERNAL_SERVER_ERROR });
    }
  })
  .addLayer(Users)
  .addLayer((context) => {
    // handle 404 errors
    const { response } = context;

    response
      .setStatus(HttpStatusCode.NOT_FOUND)
      .setBody({ message: HttpStatusReason.NOT_FOUND});
  });

Not that the order of the layers is important. The Internal Server Error layer wraps the entire layer stack and catches unhandled errors while the Not Found layer acts as the center-most layer thus intercepting requests that have not been dealt with by previous layers.

Trivia

The Ogre framework is a reference to the Shrek movie and its scene about ogres having layers, much like the framework:

Shrek: Ogres are like onions.

Donkey: They stink?

Shrek: Yes... No!

Donkey: Oh, they make you cry.

Shrek: No.

Donkey: Oh, you leave 'em out in the sun, they get all brown, start sproutin' little white hairs.

Shrek: No! Layers. Onions have layers. Ogres have layers. Onions have layers. You get it? We both have layers.

The release of the Ogre framework also coincided with the 20th anniversary of the movie.

Documentation

Full documentation

Contact

Dependents (0)

Package Sidebar

Install

npm i @ogre.dev/framework

Homepage

ogre.dev

Weekly Downloads

0

Version

1.0.6

License

MIT

Unpacked Size

65.7 kB

Total Files

38

Last publish

Collaborators

  • maxence_maire