Node API template, written in Typescript, with dependency injection

_Available on _GitHub

Features

  • Dependency Injected Everything so everything is modular and unit testable
  • Typescript everything
  • Everything testable with emulators and Docker, many examples
  • Express API with dependency injected routes, controllers and middleware
  • Firestore with transparent validation and caching
  • Websockets driven by distributed events service
  • Fail-safe and centralized configuration loading and validation
  • Flexible and configurable rate limiting
  • Flexibility over magic

Folder Structure

├── .circleci/config.yml        # Circle CI Config
├── src/                        # All application source
├──── app/                      
├────── api/                    # Root of route definition
├────── expressMiddleware.ts    # Connect express middleware and controllers to routes and express app
├────── socketMiddleware.ts     # Connect socket middleware and controllers to socket.io
├────── index.ts                # Export major app components
├────── serviceManager.ts       # COMPOSITION ROOT: https://blog.ploeh.dk/2011/07/28/CompositionRoot 
|                                 Create and connect all clients, services, middleware and controllers
├──── lib/
├────── express/                # Define all express controllers and middleware
├────── models/                 # Define all models (objects that get saved to the db or used internally)
├────── requests/               # Define all requests (objects coming into the API from outside)
├────── services/               # Define all DI classes for business logic and DB CRUD
├────── socket/                 # Define all socket controllers and middleware
|                    
├──── config.ts                 # Define the app configuration model with validation
├──── configLoader.ts           # Read configuration from env vars into configuration model
├──── logger.ts                 # Global singleton for logging to console
├──── server.ts                 # Create express app, connect to http server and serviceManager instance
├──── start.ts                  # ENTRYPOINT - Create serviceManager, use it to create and start server
|
└── Lots of random build-related files

Why Dependency Injection?

For those of you that have not heard the term before, dependency injection (or inversion of control), is a pattern wherein an object or function is passed it’s dependencies by the caller instead of requesting them directly. This improves modularity, reuse, and makes testing much easier.

Without dependency injection, any class you create would directly require it’s dependencies. This tightly binds one class to another, and means that when you are writing tests you either have to spin up the entire dependency tree and deal with all that complexity, or you have to intercept the require call.

Intercepting require calls is possible and commonly done, but not without caveats and side effects.

  • If your test blows up in the wrong way, mocked require calls may not be restored correctly before the next test.
  • Even in normal use, mocked require calls can easily contaminate other tests if not done and undone perfectly.
  • Intercepting require calls deep in the structure can be difficult and break easily and non-obviously if files are moved.
  • In the event that require-mocking fails, or mocks the wrong thing, the code will fail over to using the real instance instead of failing safe, and this can cause problems.

In my opinion, using dependency injection is just simpler for both implementation and testing.

#typescript #testing #nodejs #api

Node API template, written in Typescript
9.50 GEEK