Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specify type returned with Express response

I am trying to standardise responses in my express.js web app with TypeScript and I am not quite sure, how can I globally set, that responses should be for example this interface:

{
  success: boolean,
  data?: any,
  error?: string,
}

Right now I am just writing:

async (req: Request, res: Response, next: NextFunction) => {
  try {
    registerResponse = await register(req.body.email, req.body.password);
  } catch (error) {
    return res.json({
      success: false,
      error: error.message,
    });
  }

  return res.json({
    success: true,
    data: {
      message: 'Account registered',
    },
  });
};

Is there a way of setting additional settings, or rewriting typedefs to achieve hardcoded res.json type?

like image 838
Silverfall05 Avatar asked Mar 06 '23 18:03

Silverfall05


2 Answers

You can't remove a function defined on a type, we could add an overload for the json function with a module augmentation, but this will be of little use as if we get the properties wrong the compiler will pick the original version of the function which allows any.

A more radical approach would be to create a new type compatible with Response but which removes the original json method and replaces it with a typed version. We can use mapped types so we don't duplicate any of the original type:

// Helpers
type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;

// Generic typed response, we omit 'json' and we add a new json method with the desired parameter type
type TypedResponse<T> = Omit<Response, 'json'> & { json(data: T): Response };
// An example of a typed response
type AppResponse = TypedResponse<{
    success: boolean,
    data?: any,
    error?: string,
}>

app.get('/', async (req: Request, res: AppResponse, next: NextFunction) => {
    try {
        // ....
    } catch (error) {

        return res.json({
            success: false,
            error: error.message,
            errors: "" // causses error
        });
    }

    return res.json({
        success: true,
        data: {
            message: 'Account registered',
        },
    });

}

Unfortunately there is no way to force developers to use the generic version (other than long sticks) but with code review, this may work well enough for you.

like image 195
Titian Cernicova-Dragomir Avatar answered Mar 17 '23 04:03

Titian Cernicova-Dragomir


good solution that makes sure you have the correct API response in any TypeDoc documentation but it doesn't work if you use method chaining like: res.status(200).json(..) as you will still use the original typed json() function. So you also need to redeclare any of those methods you plan to use and make sure they return your new custom type, like this:

type TypedResponse<T> = Omit<express.Response, 'json' | 'status'> & { json(data: T) : TypedResponse<T> } & { status(code: number): TypedResponse <T> };
like image 26
Bart007 Avatar answered Mar 17 '23 04:03

Bart007