Express
In this tutorial, we'll talk about using dependency injection with Express to build a simple web API.
Setup HTTP server#
import express from "express";
import { Injector, ServiceLifetime } from "tiny-injector";
const application = express();
application.listen("8080", () => {
console.log(`${new Date()}: Server running at http://localhost:8080`);
});
Create top level express middleware that will create a new context at the beginning of a request and dispose at the end of it#
application.use((req, res, next) => {
const context = Injector.Create();
const dispose = () => Injector.Destroy(context);
// At the end the current request, everything related to that context should be garbage collected
// so you need to make sure that you let that happen by calling dispose function
["error", "end"].forEach((eventName) => {
req.on(eventName, dispose);
});
// Helper function to be able to retrieve services easily
req.locate = (serviceType) => context.get(serviceType);
// Or
req.locate = (serviceType) =>
Injector.GetRequiredService(serviceType, context);
next();
});
The Product Model#
export class Product {
constructor(public id: number, public price: number, public name: string) {}
}
Product Service#
In this snippet, the CRUD operations will be performed over the products list.
The ProductService
is registered as Singleton since we need only one instance of it.
import { Product } from "./product";
import { Injectable, ServiceLifetime } from "tiny-injector";
@Injectable({
lifetime: ServiceLifetime.Singleton,
})
export class ProductService {
/**
Seed Product
*/
private products = [
new Product(0, 10, "iPhone"),
new Product(1, 20, "MacBook"),
new Product(2, 30, "MacPro"),
];
getProducts() {
return this.products;
}
addProduct(product: Product) {
product.id = this.products.length;
this.products.push(product);
}
updateProduct(product: Product) {
const productIndex = this.products.findIndex((it) => it.id === product.id);
if (productIndex < 0) {
throw new Error(`Cannot find product with id ${product.id}`);
}
this.products.splice(productIndex, 1, product);
}
}
Product Controller#
The ProductController
is registered as Scoped because whenever a request is made new instance should be instantiated, so each request creates a fresh instance of your controller and that means a fresh instance of your variables within it.
In this example registering the controller as Transient won't make difference unless you're using scoped service as a dependency.
import { Injectable, ServiceLifetime } from "tiny-injector";
import { Product } from "./product";
import { ProductService } from "./product_service";
@Injectable({
lifetime: ServiceLifetime.Scoped,
})
export class ProductController {
constructor(private productService: ProductService) {}
getProducts() {
return this.productService.getProducts();
}
addProduct(product: Product) {
return this.productService.addProduct(product);
}
updateProduct(product: Product) {
return this.productService.updateProduct(product);
}
}
The Route Layer#
The final stage where you connect the controller to a route
Take a look at the start snippet where the setup middleware added to know how
req.locate
works
application
.all("/products")
.post("/", (req, res) => {
const productController = req.locate(ProductController);
res.json(productController.addProduct(req.body));
})
.put("/:id", (req, res) => {
const productController = req.locate(ProductController);
res.json(productController.updateProduct(req.body));
})
.get("/", (req, res) => {
const productController = req.locate(ProductController);
res.json(productController.getProducts());
});
Key Takeaways#
- We assumed that the "end" and "error" events occured when the request is fullfilled therefore we disposed the associated context.
- Context disposal at the end of each request is important to free up the memory.
- If you were intending to use the request context in an express middleware, make sure it is added after the custom set up middleware.
- It is recommended to register the controller as Scoped so it won't instantiate new instance from it when you ask for it.
Source Code: Express-Tiny-Injector