Git Product home page Git Product logo

kangojs's Introduction

KangoJS

A typescript express framework to make writing apps quicker and easier.


⚠️ Project Unmaintained️ ⚠️
This project is not currently being maintained.
One of the primary goals of KangoJS was for me to experiment with dependency injection, decorators, publishing NPM packages etc and I've now done all these things.
I'm currently of the view that further work on this project is not worth the time when better alternatives exist like NestJS. I've learnt how the wheel can be made, there's no point continuing to re-invent it!

I may come back to this project in the future or just fully unpublish the NPM modules and move/repurpose this GH org, I've not decided yet.


🤔 About

KangoJS was created because I was finding myself copying and pasting the same core functionality across a number of Express projects and I wanted a better way to manage this.
I looked into a number of existing solutions such as NestJS and OvernightJS but in the end I decided to build my own as it's a good learning opportunity for me, and I can make it function exactly how I want.

💥 Features

  • Declare routes by adding decorators to classes.
  • Concepts such as route authentication and request validation are built in.
  • Approach agnostic request logic. You are responsible for registering request validation & route authentication functions yourself which gives you the freedom to use any implementation you wish.
  • Extend the core framework with additional optional packages. See other KangoJS packages for details.

🚀 Getting Started

Install the npm package:

npm install @kangojs/core

To use decorators in typescript you will have to add the following settings to your tsconfig.json file:

"emitDecoratorMetadata": true,
"experimentalDecorators": true,

⚠️ Express is a peer dependency: As KangoJS primarily acts as a wrapper around Express, Express is kept as a peer dependency so you can manage and update it yourself independent of KangoJS.

👷 Usage

To use KangoJS you can bootstrap it with your Express app like so:

import express from 'express';
import { KangoJS } from '@kangojs/core';
import { join } from 'fs';

const app = express();

const kangoJS = new KangoJS({
  controllerFilesGlob: join(__dirname, 'src/modules/**/*.controller.{ts,js}'),
  globalPrefix: "/api/v1",
});
await kangoJS.boostrap(app);

⚠️ IMPORTANT NOTE: You must end controllerFilesGlob with .{ts,js} to ensure that KangoJS works both when running your app with ts-node and after you've compiled it to JS. See issue #2 for details.

Options

The following options are available when instantiating KangoJS:

Property Type Description Example
controllerFilesGlob string A glob pattern relative to the project root that tells KangoJS where to look for controllers. "src/modules/*.controller.ts"
globalPrefix string An optional string that will prefix all routes that KangoJS generates. "/api/v1"
authValidator (req: Request, res: Response, next: NextFunction) => any An optional middleware function that will be used when a route requires authentication. COMING SOON
bodyValidator (shape: any) => (req: Request, res: Response, next: NextFunction) => any An optional function that will be used for request body validation if you add the bodyShape property to a route. An example implementation can be found in @kangojs/class-validation.
queryValidator (shape: any) => (req: Request, res: Response, next: NextFunction) => any An optional function that will be used for query parameter validation if you add the queryShape property to a route. An example implementation can be found in @kangojs/class-validation.
paramsValidator (shape: any) => (req: Request, res: Response, next: NextFunction) => any An optional function that will be used for URL parameter validation if you add the paramsShape property to a route. An example implementation can be found in @kangojs/class-validation.

Project Structure Assumptions

KangoJS has been designed with the assumption that your app will be split into separate modules/components where each module/component has its own controller for handling Express routing.
This is based on the module structure of NestJS and the generally agreed upon best practise of Node.js app structure where applications encapsulate functionality into separate modules/components and use layers such as controllers and services to separate business logic from web request logic.

That being said, the only hard assumption KangoJS enforces is the use of controllers that have routes defined as decorated methods.

Controllers

Controllers are classes that encapsulate Express request & response logic. KangoJS attempts to load controllers from all files that match the controllerFilesGlob passed in the options.
You mark a class as a controller with the @Controller decorator and pass what path the controller will manage, for example:

import { Controller } from '@kangojs/core';

@Controller('/users')
class UserController {
  // add route methods here...
}

export default UserController;

IMPORTANT NOTE: Controller classes must be set as the default export of the file!

Routing

Routes are added to controllers by adding the @Route decorator to methods. For example:

import { Controller, Route, HTTPMethods } from '@kangojs/core';
import { Request, Response, NextFunction } from 'express';

@Controller('/users')
class UserController {
  @Route({
    httpMethod: HTTPMethods.GET,
  })
  async getAll(req: Request, res: Response, next: NextFunction) {
    return res.send('You have just attempted to fetch all users via /users [GET].');
  }

  @Route({
    path: '/:id',
    httpMethod: HTTPMethods.GET,
  })
  async get(req: Request, res: Response, next: NextFunction) {
    return res.send(`You have just attempted to fetch user ${req.params.id} via /users/:id [GET].`);
  }

  @Route({
    httpMethod: HTTPMethods.POST,
  })
  async add(req: Request, res: Response, next: NextFunction) {
    return res.send(`You have just attempted to add a new user via /users [POST].`);
  }
}

export default UserController;

The following options are available for the @Route decorator:

Property Type Description
path string An optional string to add to the end of the controllers main path.
httpMethod one of the HTTPMethods enum values. Defines what HTTP method the route uses.
requiresAuth boolean Defines if the route requires authentication (requires the authValidator to be set). For safety you must explicitly set requiresAuth:false to disable route authentication.
bodyShape any An optional property where you can pass what shape you expect the request body to have (requires the bodyValidator function to be set).
queryShape any An optional property where you can pass what shape you expect the request query to have (requires the queryValidator function to be set).
paramsShape any An optional property where you can pass what shape you expect the request URL parameters to have (requires the paramsValidator function to be set).

📚 Documentation

Additional documentation for the project can be found here.

🧰 Other KangoJS Packages

@kangojs/core (npm, codebase) is the core package that provides the base functionality for managing controllers and routes.

There are also a number of other KangoJS packages available that offer additional functionality:

Package Description NPM Link Codebase Link
@kangojs/class-validation Validate and transform request data using classes with class-transformer and class-validator. npm codebase
@kangojs/common-middleware Quickly include common Express middleware. Includes express.json(), express.urlencoded(), cors and cookie-parser. npm codebase
@kangojs/http-status-codes Provides a simple enum for HTTP status codes. npm codebase
@kangojs/serve-spa Serve single page applications such as React from your Express app. npm codebase
@kangojs/error-handler An error handler for Express. npm codebase

💬 Feedback & Contributions

I'm open to feedback and contributions. Feel free to raise an issue or suggest improvements and features.

📝 License

This project is licensed under the terms of the MIT license.

kangojs's People

Contributors

ben-ryder avatar dependabot[bot] avatar

kangojs's Issues

Remove file globbing in favour of explicit controller imports

Package Information kangojs

Describe your feature
This is very similar to #7 but instead of adding explicit controller imports alongside the file globbing it should just be replaced.

Is your suggestion related to any problems?

  • With the new dependency injection system being written more and more functionality is being moved within the core framework. For example, it's likely that v2 will see the express app declaration move within KangoJS itself rather then the app be defined and managed by the end user. If users will be passing middleware and other things directly to KangoJS it makes sense that controllers should follow this same pattern.
  • As I start to add tests to the project the file globbing feature will make it much harder to test the framework as it's much harder to add specific controllers during tests.
  • Removing the feature will stop things like #2 from being an issue and removes "magic" functionality and makes the apps created using KangoJS easier to reason about.

Additional context
n/a

Decide what DTO design pattern class-validation is optimizing for

In a project using class-validation I'm facing a design pattern decision/question about the use of "DTO" classes.

Right now in that project DTO classes exist and are used to describe & validate request data. The same DTO classes are also used to describe the data passed from controllers -> business logic services -> database services.

The issue is my request data shape is now diverging from the data shape required by the database service. This means I now need multiple "DTO" classes for a single action:

  • one for validating request data at the controller layer and describing the interface between the controller and business logic service
  • one for describing the interface between the business logic layer and database service layer

This is fine, but I'm finding it somewhat confuses the use of the phrase "DTOs". My solution so far has been to now have a distinction between "shapes" and "dtos":

  • "Shapes" are classes defining the request data shape and are decorated with class-validator decorators
  • "DTOs" are classes that are purely for transferring data

None of this really relates to class-validation too much, however I'm thinking that it could be worth while removing all references to "DTOs" from class-validation.

Add middleware option to controller & route decorators

Package Information

  • Name: core, v2

Describe your feature
Add middleware option to controller & route decorators so middleware can be added to specific routes and route groups.

Is your suggestion related to any problems?
You can only add middleware globally for the applciation, but you might want to only add middleware to a specific route or middleware.

Additional context
n/a

Look for Lerna alternative

Describe your improvement
Look for Lerna alternative or simply remove the tools.

Is your suggestion related to any problems?
I'm not really using Lerna. It's not fitting with my workflow and and can't customize it how I would like.
I either need to find an alternative, write some custom scripts or just not bother with any tooling.

Additional context
n/a

Add req.params validation to class-validation

Describe your feature
Add req.params validation to class-validation. This would work exactly the same way as body & query validation.

Is your suggestion related to any problems?
There is currently no way to validate URL parameters

Additional context
n/a

Implement a dependency injection system

Package Information: kangojs

Describe your feature
Implement a dependency injection (DI) / Inversion of Control (IoC) system into KangoJS.
This would mean you could mark dependencies to inject into controlllers, services etc and KangoJS would handle the depedency creation and injection.
This DI must be recursive, so the application must work down the dependency tree (controller -> service -> other services/helpers etc) and handle injecting everything.

Is your suggestion related to any problems?
For testing (unit tests and e2e) you need to be able to access the dependency instances that are part of the application and also potentially overwrite / mock dependencies.

This will be useful for a number of things during testing:

  • For database setup/teardown during e2e testing you could re-use the same database service that the application uses to create content rather than having to write custom things for testing or creating duplicate instances.
  • Testing certain states may rely on knowing/editing internal application state so having access to the same dependency instance that is being used by the services/controllers could let you view/edit application state for tests.

Additional context
I think this would be done the same / a similar way to how it's done in NesJS or InversifyJS:

import { Inject } from "@kangojs/core";
import { UserService } from "./users.service";

export class UserController {
    constructor(
        @Inject(UserService) userService: UserService
    )
}
import { Inject } from "@kangojs/core";
import { UserDatabase } from "./users.database";

export class UserService {
    constructor(
        @Inject(UserDatabase) userDatabase: UserDatabase,
        @Inject(RandomService) randomService: RandomService
    )
}
import { Inject } from "@kangojs/core";

export class UserDatabase {
    constructor(
        @Inject(Config) config: Config
    )
}

Some notes:

  • @Inject decorator can be used to mark a dependency as injectable and tells KangoJS what class should be instantiated, passed to the class and stored in a centeral IoC container for later use.
  • I will need to consider if/how to implement singleton injection vs multiple instance injection / factories etc.
  • DI will work using an IoC container, meaning dependencies will be stored/managed in one centeral container and injected from this container. This means that there could be an interface to let you fetch dependencies from the KangoJS class. For example:
const kangoJS = new KangoJS();
await kangoJS.bootstrap();

const userDatabase = await kangoJS.getDependency<UserDatabase>(UserDatabase);

Maybe there could also then be a way to manually add/overwrite dependencies too? I don't think this would be a paticualr focus for an MVP of this feature though:

const kangoJS = new KangoJS();
await kangoJS.bootstrap();

class MockUserDatabase implements UserDatabaseInterface {
      isUserValid(user: User) {
             if (user.id === "test-user-id") {
                    return true;
             }

             return this.super.isUserValid(user);
      }
}

// Set dependency to use, but still let KangoJS handle instantiation via the DI system
// as maybe MockUserDatabase still relies on other injected services
await kangoJS.setDependency(UserDatabase, MockUserDatabase);

// Maybe you'd also need a way to full overwrite/bypass KangoJS's DI system
await kangoJS.setDependencyInstance(UserDatabase, new MockUserDatabase(new TestConfig()));

Consider moving error-handler into the core framework

Package Information: kangojs, error-handler

Describe your improvement
It's likely that the error handler (or a version of it) will be used on every project using KangoJS, so why not just move this functionality to the base framework or at least add some functionality like I have for request auth/validation to make this less custom/external to the framework.

Is your suggestion related to any problems?
Reduces boilerplate and repeated code, error handling will probably rely on dependency injection so it makes sense to possibly absorb the functionality

Additional context
n/a

useErrorHandlerMiddleware doesn't act like other functions.

Package Information: [email protected]

Describe the bug
Various other use... function in KangoJS all work by passing in the express app like useX(app, config) but useErrorHandlerMiddlewaredoesn't. It should work the same way to keep things consistent.

To Reproduce
Attempt to use useErrorHandlerMiddleware and you will see it must be used like app.use(useErrorHandlerMiddleware(config)); rather than useErrorHandlerMiddleware(app, config).

Expected behaviour
Error handling middleware should be setup with useErrorHandlerMiddleware(app, config)

System Information
n/a

Additional context
n/a

Module could not be found when using jest

Package Information:: kangojs @ all versions

Describe the bug
When attempting to use a KangoJS app in jest testing you get a "module could not be found in " error.

To Reproduce

  1. Create a KangoJS app
  2. Attempt to use this app in a jest test

Expected behaviour
The module should be found and the test should work.

Additional context
n/a

Add a declarative way to define controllers

Package Information: kangojs, all versions

Describe your feature
Add a declarative way to define controllers, which means that rather than solely relying on file globbing to find and load them you could simply pass a list of controllers when instanciating or bootstrapping KangoJS.

Is your suggestion related to any problems?
Application startup feels quite slow when using KangoJS. I think this might be because of how long it takes for the file globbing to work. If controller classes could directly be passed to KangoJS this might speed up start times.

Additional context
It would be intresting to measure how long start times take before and after this feature is added. If it actually doesn't make much of a difference then it might not be worth adding the feature.

This feature would also introduce multiple ways of doing the same thing, it might be best to just pick one?

Controller loading doesn't work in compiled apps

Package Information: kangojs, all versions

Describe the bug
When passing a controllerFilesGlob to KangoJS it will be a glob of typescript files, for example src/modules/**/*.controller.ts.
When the app is compiled to JS this reference stays the same and so the compiled app fails to import the controllers as Typescript files can't be imported into the compiled JS.

To Reproduce
Steps to reproduce the behaviour:

  1. Setup KangoJS with a controller files glob that points to typescript files (such as src/modules/**/*.controller.ts).
  2. When running via ts-node this will work fine.
  3. When attempting to run the compiled JS app an error will occur.

In my application the error is as follows:

err: {
  "type": "SyntaxError",
  "message": "Cannot use import statement outside a module",
  "stack":
      /home/<path>/api/src/modules/albums/album.controller.ts:1
      import { NextFunction, Response } from 'express';
      ^^^^^^
      
      SyntaxError: Cannot use import statement outside a module
          at wrapSafe (internal/modules/cjs/loader.js:984:16)
          at Module._compile (internal/modules/cjs/loader.js:1032:27)
          at Object.Module._extensions..js (internal/modules/cjs/loader.js:1097:10)
          at Module.load (internal/modules/cjs/loader.js:933:32)
          at Function.Module._load (internal/modules/cjs/loader.js:774:14)
          at Module.require (internal/modules/cjs/loader.js:957:19)
          at require (internal/modules/cjs/helpers.js:88:18)
}
             

Expected behaviour
I'm unsure at the moment. Ideally the solution should not require any changes from a user perspective.
Can KangoJS somehow hook into the typescript build to change that string?
When loading could KangoJS use tsconfig.json to transform the path if it detects it's running with JS not TS?

Additional context
This could just be a fundamental problem with typescript & builds. It will probably be a good idea to see if this is an
issue other frameworks or libraries have needed to solve.

Add Error Handler Package

Describe your feature
Add an error handler package (kangojs/error-handler).
It should have these features:

  • Expose a middleware to handle errors
  • Follow NodeJS good practices and handle errors separate to the middleware
  • It should expose common error types that can be used in applications
  • Errors should be mapped to HTTP responses in the middleware. HTTP knowledge should only be needed at the controller/middleware layer.

Is your suggestion related to any problems?
n/a

Additional context
This could be used with a package like @kangojs/logger to prettify error output?

Add tests to core for nested dependency injection

Package Information: core @ 2*

Describe your feature
Add unit tests for nested/recursive dependency injection.

For example if DependencyA is registered to the DI system and it requires DependencyB which requires DependencyC then DependencyB and DependencyC should also be registered to the DI system and the instance of DependencyA should be passed the correct instance of those dependencies (depending on the injection mode).

Is your suggestion related to any problems?
This is a critical part of the DI system that currently has no test coverage.

Additional context
n/a

Implement direct data return from controller classes

Package Information: core @ *

Describe your feature
RIght now the controller methods that KangoJS binds to routes have no special handling which means you still have boilerplate code with sending responses and forwarding errors to next():

    @Route({
        httpMethod: HTTPMethods.POST,
        bodyShape: CreateNoteShape
    })
    async add(req: RequestWithUser, res: Response, next: NextFunction) {
        let newNote: NoteDto;

        try {
            newNote = await this.notesService.add(req.user.id, req.bodyDto);
        }
        catch (e) {
            return next(e);
        }

        return res.send(newNote);
    }

It would be great if the framework could absorb some of that repeated handling, so you could just do something like this instead and the returned data is sent in a response automatically and thrown errors are caught are handled:

    @Route({
        httpMethod: HTTPMethods.POST,
        bodyShape: CreateNoteShape
    })
    async add(req: RequestWithUser, res: Response, next: NextFunction) {
        return await this.notesService.add(req.user.id, req.bodyDto);
    }

Users should be able to still send a response manaully themselves though.

This will require a bit of discover work:

  • How will error handling work in terms of unhandled promise rejections etc, will it work fine? (maybe user's should return promises directly, not return await?)
  • Will core need to register the error handler the same way it does currently for auth and validation handlers?
  • How can I make it posisble for users to still manually return a response as well as allowing for automatic response handling?

Is your suggestion related to any problems?
Controllers have a lot of repeated boilerplate code around response & error handling.

Additional context
n/a

Seperate out the key interfaces/data into a seperate @kangojs/common package

Package: kangojs/common

Describe your improvement
Separate out common interfaces/data into a common package:

  • Error identifiers
  • HTTP status codes

Is your suggestion related to any problems?
A front end package requiring the error identifiers shouldn't need to pull in the entire core package, even if the client will sort tree shaking itself.

Right now a client also need to install express & cookie-parser types to which it shouldn't need

Additional context
n/a

Consider moving common-middleware into the core framework

Package Information: kangojs, common-middleware

Describe your improvement
It's likely that common-middleware will be used on every project using KangoJS, so why not just move this functionality to the base framework.

Is your suggestion related to any problems?
Reduces boilerplate and repeated code

Additional context
n/a

Express should be a peer dependency of kangojs

Package Information

  • Name: kangojs
  • Version (as installed from npm): all

Describe the bug
Express should be a peer dependency.
KangoJS is suppose to mainly be a convenience wrapper around Express, it shouldn't bring it's own Express dependency but instead use the one in the project already.

Expected behaviour

  • Express should be a peer dependency.
  • Installation docs should be updated to reflect this

Add service class interface with helper methods

Package Information: core

Describe your feature
Add a base service class interface which has the methods async onStart() and async onKill(). These are like "hook functions" which KangoJS can call at certain points of the application lifecycle.

  • onStart - called once the application is setup and ready. This could be used for things like starting up cron tasks etc
  • onKill - called when the application is being killed. This can be used for clean up such as closing connections to the database/Redis etc

Is your suggestion related to any problems?
For testing I need to manually fetch all services that require clean up and handle each one.
Having a built in way to handle this would make it easier when multiple services need clean up.

Additional context
n/a

Refine error-handler and logger

Package Information: core @ v2

Describe your improvement
The error handling and logger feels a bit clunky to use, especially when debugging.
Can this be improved or simplified?

Is your suggestion related to any problems?
Debugging can be a bit clunky and hard with the custom error handling

Additional context
n/a

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.