Whenever a request comes into our web server, we have to determine what response we want to send back. What we are building is going to respond to post and receive requests, which is similar to the way we build REST APIs. The ability to route incoming requests to responses is known as routing. Our application is going to handle three types of request:
- A POST request with add as the URL (in other words, when we see http://localhost:3000/add/). This will add an image and the associated details to the database.
- A GET request with get in the URL (as in http://localhost:3000/get/). This gets the IDs of all the saved pictures and returns an array of these IDs back to the caller.
- A GET request with /id/ in the URL. This uses an additional parameter in the URL to get the ID of the individual picture to send back to the client.
The destination of each request corresponds to a unique action that we want to take. This gives us a hint that we should be able to split each route into a single class that does nothing but service that action. To enforce the single action, we define the interface that we want our routing classes to use:
export interface IRouter {
AddRoute(route: any): void;
}
We are going to add a helper class that will be responsible for instantiating each router instance. The class starts off simply enough, creating an IRouter array that the route instances will be added into:
export class RoutingEngine {
constructor(private routing: IRouter[] = new Array<IRouter>()) {
}
}
Things get interesting with the method that we use to add the instances in. What we are going to do is accept a generic type as a parameter and instantiate the type. To do this, we must take advantage of a TypeScript feature that allows us to accept a generic type and specify that when new is called on it, it returns an instance of the type.
As we have specified a generic constraint on our type, we will only accept IRouter implementations:
public Add<T1 extends IRouter>(routing: (new () => T1), route: any) {
const routed = new routing();
routed.AddRoute(route);
this.routing.push(routed);
}
Now that we have our routing support in place, we need to write the classes that correspond with the route requests we identified previously. The first one that we are going to look at is the class that accepts the add post:
export class AddPictureRouter implements IRouter {
public AddRoute(route: any): void {
route.post('/add/', (request: Request, response: Response) => {
}
}
This method works by stating that when we receive an /add/ post, we will take the request, process it, and send a response back. What we do with the request is up to us, but whenever the routing determines that we have a match here, we will execute this method. In this method, we are going to create a server-side representation of the picture and save it to the database.
For the purposes of this application, we have only introduced Express routing. Angular has its own routing engine, but for the purposes of what we wanted to put in place in our code, we have no need for it. In Chapter 5, Angular ToDo App with GraphQL and Apollo, we introduce Angular routing.