@summit-credit-union/create-express-app v1.1.0
create-express-app
This repo is meant to be used as an initializer with npm init
It should create a new boilerplate ExpressJS app configured for deployment in SCU's K8s environment
Before You Begin
You'll want to make sure you have to have npm and git configured
On Windows?
Group Policy will block the init script on Windows because npm uses CMD to run scripts by default
so can we change what npm uses to run scripts?
yes, npm config set script-shell
do we have an alternative available on our Windows machines capable of running the init script?
yes, Git Bash
Where does the Git Bash executable live?
C:\Program Files\git\bin\bash.exe
so all we need to do is
npm config set script-shell "C:\\Program Files\\git\\bin\\bash.exe"
then the init script will be allowed to run
Initialization
From a command-line/terminal, run
npm init git+ssh://git@github.com:summitcu/express-app.git <name-of-project>
That will run several commands on your behalf to pull code and install dependencies in the <name-of-project>
directory.
It will also ask you for some extra information it needs to finish the setup.
Once completed, you can move into the newly created <name-of-project>
directory and run
npm run start
You should eventually see a message on the terminal server started at http://localhost:8200
,
and to verify you should be able to open a browser to http://localhost:8200
What's All Included?
ESLint
TypeScript
Aliases
Express
Inversify
Request Id
Logging
Winston
Morgan
Controllers
Ping HealthCheck
Swagger / OpenAPI
JSDoc
Mocha
Chai
Docker
FAQ
Where is nodemon?
we can use nodemon for local development if we would like (so we can have live reloads like the Vue projects), but because we are using Docker when these get deployed we don't need to rely on nodemon.
What is up with @inject and @injectable?
Before we dive into the code, let’s take a moment to talk about two important concepts: Inversion of Control (IoC) and Dependency Inversion (DI). These are key principles from SOLID that help us write more maintainable and flexible code.
Inversion of Control (IoC): This is all about moving the control of objects outside of the class that uses them. It’s like saying, "Hey, I’m not going to create my own objects, someone else will give them to me."
Dependency Inversion Principle (DI): This principle tells us that high-level modules should not depend on low-level modules. Both should depend on abstractions. In other words, "Depend on abstractions, not on concrete classes."
Now, let’s see how these principles apply to our code.
Ever had one of those days where you're working with an interface like this?
export interface CarRepository {
list?(): Array<Car>
}
And you've got a class, say CarPark, that looks a bit like this:
import { CarRepository } from './interfaces'
class CarPark implements CarRepository {
list(): Array<Car> {
// implementation to fetch Car list from specific source
}
}
All's well and good, right? Well, until the source of that Car list changes.
Imagine you started your project fetching data from a MongoDB instance, but then you decided to switch to Flat Files. We might create a CarMongo class like
import { CarRepository } from './interfaces'
export class CarMongo implements CarRepository {
list(): Array<Car> {
// MongoDB implementation here
}
}
and then refactor our CarPark to
import { CarRepository } from './interfaces'
class CarPark {
constructor(
private repo: CarRepository
) {}
list(): Array<Car> => repo.list()
}
That would allow us to pass in any CarRepository
when creating a new instance of CarPark
.
So we have a bunch of new CarPark(new CarMongo())
all through our code.
Which means we would have to find ALL of those
and update each and every one of them
when we decide to use Flat Files.
That could mean changing hundreds of methods in dozens of classes. 😱
But don't worry, @inject and @injectable are here to save the day!
@injectable is a decorator from the Inversify library. It lets you mark a class as "injectable" using the container.
import { injectable } from 'inversify'
import { CarRepository } from './interfaces'
@injectable()
export class CarMongo implements CarRepository {
list(): Array<Car> {
// MongoDB implementation here
}
}
Wait, container? What's that?
Well, Inversify is the container. 📦
To set up the container, we first need our symbol
export const Locator = {
CarRepository: Symbol.for('CarRepository')
}
Then we can use that symbol to bind our CarRepository interface to the CarMongo implementation in our container.
import { Container } from 'inversify'
import { Locator } from './locator'
import { CarRepository } from './interfaces'
import { CarMongo } from './impl'
export const container = new Container()
container.bind<CarRepository>(Locator.CarRepository).to(CarMongo)
Now we can refactor our actual CarPark class to @inject the CarRepository from our container using the symbol for CarRepository.
Which ends up looking something like this:
import { inject } from 'inversify'
import { Locator } from './locator'
import { CarRepository } from './interfaces'
class CarPark {
constructor(
@inject(Locator.CarRepository) private repo: CarRepository
) {}
list(): Array<Car> => this.repo.list()
}
So, what's the big deal with all this extra code? This looks just like how we originally refactored CarPark.
Well, when we inevitably decide to switch data sources, all we need to do is create an @injectable class for the Flat File implementation.
@injectable()
export class CarFlatFile implements CarRepository {
list(): Array<Car> {
// some logic using flat files
}
}
And update our container to point to it.
import { Container } from 'inversify'
import { Locator } from './locator'
import { CarRepository } from './interfaces'
import { CarFlatFile } from './impl'
export container = new Container()
container.bind<CarRepository>(Locator.CarRepository).to(CarFlatFile)
The CarPark class and anything using it wouldn't even notice the change. This keeps the logic of the application isolated from the integration with external dependencies. 🎉
And that's it! Hope this helps you keep your code clean and maintainable. Happy coding! 👩💻👨💻