@mayajs/router

Simple NodeJS routing library created using Typescript

Usage no npm install needed!

<script type="module">
  import mayajsRouter from 'https://cdn.skypack.dev/@mayajs/router';
</script>

README

MayaJS Router

Version Downloads License Code Style

Lightweight, simple and fast NodeJS Router developed using Typescript.

Motivation

We develop this library to be used as the main router for MayaJS RESTful API framework. As much as possible we want to have a full control of the libraries we used in our main projects for maintainability and ease of transition for future updates.

Roadmap

You can see the detailed roadmap of the project here.

Suggestions

For suggestions and features you want to see in the future you can visit our issues section.

Getting started

Before installing this package make sure to download and install Node.js 10 or higher.

Setup Node

To check your node version

npm -v

For new project kindly initialize npm using the command below or see documentation here.

npm init

Setup Typescript

Install typescript and typescript watcher

npm i typescript ts-node-dev -D

Initialize typescript

tsc --init

Setup Router

Install mayajs router

npm i @mayajs/router

Add start script on your package.json

"scripts": {
  "start": "tsnd src"
}

Features

  • Simple and easy
  • Lightweight library
  • Built with TypeScript
  • No third party library
  • Modules for shareable code
  • Dependency Injection and IOC
  • Fast execution with route caching
  • Supports beginners to advanced developers
  • Function and Class based coding style
  • Supports third party middlewares like ExpressJS

Quick Start

The easiest way to get started is to create a simple route. Create a src folder inside your project folder. Inside the src folder create an index.ts file. Copy the code below into index.ts file.

import maya from "@mayajs/router";
import http from "http";

const PORT = 3000;
const app = maya();

app.add([
  {
    path: "/",
    GET: () => "Hello, World",
  },
]);

http.createServer(app).listen(PORT, () => console.log(`Server listening on port ${PORT}.`));

RUN

To run your mayajs project open a terminal inside your current project and run the command below.

npm start

ROUTE

Defining routes in MayaJs is like creating a JSON object.

const user = {
  path: "user",
  GET: () => {
    //  ...
  },
  POST: () => {
    //  ...
  },
  PUT: () => {
    //  ...
  },
  PATCH: () => {
    //  ...
  },
  DELETE: () => {
    //  ...
  },
  OPTIONS: () => {
    //  ...
  },
};

As seen above each method name has a callback function. Mayajs will set the correct content-type based on what the method callback will return.

const route = {
  path: "user",
  GET: () => {
    // This will return `undefined`
    // This will have a content-type of "text/plain"
  },
  POST: () => {
    return "Hello"; // This will have a content-type of "text/plain"
  },
  PUT: () => {
    return { message: "Hello" }; // This will have a content-type of "application/json"
  },
  PATCH: () => {
    return "<h1>Hello</h1>"; // This will have a content-type of "text/html"
  },
};

Incase you need to use a middleware for an specific route method you need to create a route object instead.

const route = {
  path: "user",
  GET: {
    middlewares: [middleware],
    callback: () => {
      // ...
    },
  },
};

PARAMS

MayaJs passes a context object on each route method. You can acces the route params by destructuring the context object on the callback method.

const route = {
  path: "user/:id",
  GET: ({ params }) => {
    // `params` object contains the value of `id` define on the path above e.g. `user/hello`
    return params.id; // This has a value of "hello"
  },
};

QUERY STRING

You can also access the query string using the context object provided by MayaJs on your route method callback function.

const route = {
  path: "user",
  GET: ({ query }) => {
    // `query` object contains all the value on your query string e.g. `user?id=1`
    return query.id; // This has a value of 1
  },
};

BODY

Request body is also accessible in the context but you need a middleware before you can actually uses. There is a popular middleware called body-parser that we can use to achieve this.

import maya from "@mayajs/router";
// Third party middleware you need to install
import bodyParser from "body-parser";

const app = maya();

// Initialize body parser for mayajs to use it internally
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

const route = {
  path: "user",
  POST: ({ body }) => {
    // `body` object contains all the value from the request body
    return body; // This has a value of req.body
  },
};

app.add([route]);

MIDDLEWARES

As seen above how we used third party middlewares to parse the request body. We can also create our own middlewares. Although MayaJs supports all ExpressJs Middlewares, we also can create a MayaJsMiddleware of our own. The context is where you can access params, query and body objects.

router.use((context, next, error) => {
  // This `error` variable comes from the previous middleware
  console.log(error);

  // The `next` function will trigger the end of this callback function
  // and execute the next middleware in the list
  next("Error message or any value that you want to pass to the next middleware");
});

CHILD ROUTES

A child route is a route that appended to its parent route. It can be added using the children property from the route object.

app.add([
  {
    path: "parent", // This is the `parent` route
    children: [
      {
        path: "child", // You can call this child route by `parent/child`
        GET: () => "This is from a child route",
      },
    ],
  },
]);
NOTE: A `child` route can also have its own `children`.
app.add([
  {
    path: "parent", // This is the `parent` route
    GET: () => "This is a parent route",
    children: [
      {
        path: "child", // You can call this child route by `users/child`
        GET: () => "This is a child route",
        children: [
          {
            path: "grandchild", // You can call this child route by `parent/child/grandchild`
            GET: () => "This is a grandchild route",
          },
        ],
      },
    ],
  },
]);

CONTROLLER

A controller is a class that act as a replacement for the route method objects. It can be used to make your code more modular and manageable when your code base get bigger.

class UsersController extends Controller {
  GET() {
    return "Hello form UsersController";
  }

  // You can also add additional route method like this
  POST() {
    // ...
  }

  // Other routes available are PUT, PATCH, DELETE and OPTIONS
}

app.add([
  {
    path: "user",
    controller: UsersController,
  },
]);

If you want to add middleware a middleware to a specific method you can add the middleware property in a controller.

class UsersController extends Controller {
  middleware = {
    GET: [middleware], // This middleware will be added to the `GET` method below
  };

  GET() {
    //  ...
  }
}
NOTE: A controller has no `children` property

SERVICES

A service is an injectable class that can be used inside of a controller or another service.

export class UsersServices extends Services {
  // Every function inside the service can be accessed by the class its injected
  // All public properties are also accessible
  // A service is instatiated once and making it a singleton

  add() {
    // ...
  }
}

class UsersController extends Controller {
  constructor(userServices: UsersServices) {
    super();
  }

  addUser() {
    // This `add` function coming from the `UsersService`.
    // It is accessible on this controller via dependency injection.
    // MayaJs automatically instantiated it and created it singleton instance.
    this.userServices.add();
  }
}

MODULE

A module is a like a container of routes, controller and services that act on its own. This will allow your code to modular and shareable accross all routes.

class UsersModule extends MayaJsModule {
  // List of `module` or `services` that this module will use for its controllers
  imports = [];
  // List of controllers that this module will use
  declarations = [];
  // List of services that can be use inside this module
  providers = [];
  // List of services that this module will exports that can be used by other modules
  exports = [];
  // List of dependencies that will be injected to this module
  dependencies = [];
}

As you can see when we create a module we dont have any way to add routes on it. Below is an example of implementation of how to add a RouterModule for this module to add routes.

import { UsersController, UsersIdController } from "./controller";
import { RouterModule } from "@mayajs/router";
import { UsersServices } from "./services";

// Define the routes
const routes = [
  {
    path: "/",
    controller: UsersController,
  },
  {
    path: ":id",
    controller: UsersIdController,
  },
];

export class UsersModule extends MayaJsModule {
  // User `RouterModule.forRoot` to inject the routes
  imports = [RouterModule.forRoot(routes)];
  // Declare the controllers that will be used inside the routes
  declarations = [UsersIdController, UsersController];
}

CUSTOM MODULE

Unlike the common mayajs module, custom modules provide other functionality aside from managing your controllers. One example of this the RouterModule that is reponsible for adding routes to your module. A key concept of a custom module is it has an invoke and forRoot function that provides additional functionalities.

export class UsersModule extends CustomModule {
  invoke() {
    // This function will run after an instance of this module is created
    // But the providers will stay singleton on each instance
  }

  static forRoot(options: any) {
    return {
      module: UsersModule, // Returns the current module reference
      providers: [], // List of providers that this module will use on its controllers
    };
  }
}

Now we know how to create a module but how we can call it on our routes. We are now introducing to loadChildren property of our route object. MayaJs will automatically resolve the module asynchronously when the path is added on the route list.

import maya from "@mayajs/router";
import http from "http";

const app = maya();

app.add([
  {
    path: "users",
    // We are using dynamic importing so we don't need it to manually import the module in the file
    // MayaJs will resolve the module asynchronously
    loadChildren: () => import("./users.module").then((m) => m.UsersModule),
  },
]);

Collaborating

See collaborating guides here.

People

Author and maintainer Mac Ignacio

License

MIT