'TS: How to ensure generic type on Express Response

I have an interface that has a method The method has a parameter The parameter has a type that takes a generic The method has a return, which also receives a generic A class implements my interface, and therefore implements the method I need to ensure that:

  • The method parameter can only receive a type that receives the same generic defined in the interface;
  • The method return can only receive a type that receives the same generic defined in the interface;
  • In the interface, I just need to declare the type in the parameter, and use the same type inside a Promise in the return; This one is desired, but not required.

Straight to the code:

import { Request, Response } from 'express';

interface Foo {
  foo: string;
}

interface Bar {
  bar: string;
}

interface Controller<T> {
  create: (
    req: Request, // not important to the question
    res: Response<T | Bar>
  ) => Promise<Response<T | Bar>>;
}

The code below works fine:

class MyController1 implements Controller<Foo> {
  create = async (
    _req: Request,
    res: Response<Foo | Bar>
  ): Promise<Response<Foo | Bar>> =>
    res.json({ bar: 'bar' });
}

And if I try, for example, to remove the Bar option in the return type, the TS Compiler complains (and it is okay):

class MyController2 implements Controller<Foo> {
  create = async ( // Error on this line, incorrect property
    _req: Request,
    res: Response<Foo | Bar>
  ): Promise<Response<Foo>> => // I can't remove the `Bar` option
    res.json({ bar: 'bar' });
}

However, if I remove the type of the Response (both in return type and in res type), i get no TS Compiler errors:

class MyController3 implements Controller<Foo> {
  create = async (
    _req: Request,
    res: Response, // Removed response type
  ): Promise<Response> =>
    res.json({ ahdiashda: 'string' }); // Not `foo` nor `bar`, but no TS error
}

How can I create an interface Controller that, when implemented, it ensures the method' signature, so I can have something like (or the close as possible to):

// SOLUTION 1
class MyController4 implements Controller<Foo> {
  create = async (_req, res) => // I want no error because of lack of type on parameters
    res.json({ ahdiashda: 'string' }); // I want an error because it's not `Foo` nor `Bar`
}
class MyController5 implements Controller<Foo> {
  create = async (_req, res) =>
    res.json({ foo: 'string' }); // I want NO error
}

// OR SOLUTION 2
class MyController6 implements Controller<Foo> {
  create = async (_req: Request, res: Response) =>
    res.json({ ahdiashda: 'string' }); // I want an error because it's not `Foo` nor `Bar`
}
class MyController7 implements Controller<Foo> {
  create = async (_req: Request, res: Response) =>
    res.json({ foo: 'string' }); // I want NO error
}

SOLUTION 3: you have another class implementation idea on how to implements the same controller interface, ensuring the types but with no code duplication?

PS: The problem with MyController1 is that I have to repeat the type of res and the return value. If I create some 10 or 20 Controllers, if the interface changes, I will have to update the types in 10 or 20 places, violating the DRY.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source