npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

express-controller-pattern

v0.2.3

Published

Having worked a lot with typescript and express I just felt that something was missing in the way routes are registered and found that there are a few articles on how to use decorators for route creation with express, but I did not find any recent, up-to-

Downloads

4

Readme

Express controller pattern

Having worked a lot with typescript and express I just felt that something was missing in the way routes are registered and found that there are a few articles on how to use decorators for route creation with express, but I did not find any recent, up-to-date npm packages that solved this in a way I found practical.

I like the way that C# and other languages set up routing, using controllers - so I started reading articles on the web and after reading this article, I was inspired to create an npm package that uses decorators for routing using express.

Documentation

Creating a controller with routes

A controller is a class decorated with @Controller(routePrefix). The routePrefix is prepended to all routes in the controller class. A route can be created by decorating a class method with @Route(path, httpVerb) or one of the following convenience decorators that work as aliases for @Route-

  • @GET(path) -> @Route(path, "get")
  • @PUT(path) -> @Route(path, "put")
  • @POST(path) -> @Route(path, "post")
  • @HEAD(path) -> @Route(path, "head")
  • @DELETE(path) -> @Route(path, "delete")
  • @OPTIONS(path) -> @Route(path, "options")

All decorators also accepts optional middleware functions that are applied in the following order:

  1. Controller wide middleware, via @Controller, are applied first and applies to all routes in the controller
  2. Route specific middleware, via @Route, @GET, @POST, etc. are applied only for the given route, after any middleware that has been applied controller wide

Accessing request data

To access request data, a simple form of injection is in place which can be used to select e.g. the request body, path params or query params. It is also possible to inject the express Request and Response objects. The following examples shows how to use injection, given a very simple CRUD API:

@Controller("/api/something")
class SomethingController {
  public list(@QueryParam("filter") filter: string) {
    /* ... */
  }
  public create(@Body body: any) {
    /* ... */
  }
  public read() {
    /* ... */
  }
  public update(@Param("id") id: string, @Body body: any) {
    /* ... */
  }
  public delete(@Param("id") id: string) {
    /* ... */
  }
}

It is also possible to access the express Request and Response objects using @Req and @Res decorators:

@Controller("/api/something")
class SomethingController {
  public doSomething(@Req req: Request, @Res res: Response) {
    /* ... */
  }
}

Handling responses

There are multiple ways to handle responses, the simplest way is to return an object or a promise from the controller method. For more fine grained control it is possible to use the Request and Response objects from express directly as show in the previous section or return an instance of HttpResponse from the method. All HttpResponse methods returns the instance ifself so multiple calls can easily be chained, see the sample controller in the next section.

Example controller

A simplified controller that uses a little bit of everything that has been explained above. While some functions and interfaces have not been implemented the example should still give an idea of the different usecases.

import {
  Controller,
  GET,
  POST,
  OPTIONS,
  QueryParam,
  Body,
  Param,
} from "express-controller-pattern";

// Add a controller wide middleware for logging requests to this controller, prefixed with the class name
@Controller("/api/user", [createLoggerMiddleware("UserController")])
export class UserController {
  @GET("/")
  public async getUsers(@QueryParam("filter") filter): Promise<User[]> {
    // returns a promise that resolves to an array of all users
    return userDbClient.getAllUsers({ filter });
  }

  // Add middleware to only this route, that prevents unauthorized access
  @POST("/", [isAuthorized])
  public createUser(@Body user: User) {
    // validation returns an HttpResponse with status 400 on missing parameters
    if (!user.firstName || !user.lastName) {
      return new HttpResponse().status(400).json({
        message: "First name and last name are required",
      });
    }

    // returns a promise that resolves to the created user
    return userDbClient.create(user);
  }

  @OPTIONS("/")
  public getUsersOptions(): HttpResponse {
    // returns an HttpResponse with custom header - calling end is necessary on requests that do not have a body
    return new HttpResponse().setHeader("Allow", "GET,POST,OPTIONS").end();
  }

  @GET("/:id")
  public async getUser(@Param("id") id: string): Promise<User> {
    // returns a promise that resolves to an object containing the specific user
    return userDbClient.getUser(id);
  }

  /* ... other methods for updating, deleting and modifying a user ... */
}

To register controllers use the registerControllers method and call it with the express application and a list of controllers that should be registered.

import express from "express";
import { registerControllers } from "express-controller-pattern";

const app = express();

registerControllers(app, [
  UserController,
  SomethingController,
  /* ... */
]);

app.listen(3000);

Ideas for improvement

  • Auto import controllers in folder
  • Injection in middleware similar to the way it works in controller