Express.js - cast interface by body inside request object

Viewed 3795

I written a similar ground to ASP.NET MVC structure and I released on my github (express-mvc). Everything is ok but I can't cast body inside the request object to fit any interface.

This is exactly what I want:

export default interface Post {
    title: string
    body: string
}
import Post from '@models/user'

router.post("/post", (req: Request, res: Response) => {
    const Model: Post = req.body
})

but didnt work interface on runtime because no interface on JavaScript. When I use this, will be output in JavaScript

const Model;
Model = req.body

When it's that way, it assignment non-members property of interface to my variable.

In short, I want known properties of interface in Model variable.

I'm using destructuring to solve the problem temporarily in this project

import Post from '@models/user'

router.post("/post", (req: Request, res: Response) => {
    const { title, body }: Post = req.body

    const Model: Post = <Post>{
        title,
        body
    };
})

Do you have any suggestions


Solve problem with classes

function castByBody<Model>(source: {}, modelProperties: any[]): Partial<Model> {
    const post: Partial<Model> = {};
    for (const key of modelProperties) {
        post[key] = source[key];
    }
    return post;
}

class User {
  public first_name: string = null
  public username: string = null
  public email: string = null
  public password: string = null
  public age: number = null
}

let user = new User() // create new instance
let userProperties: any[] = Object.getOwnPropertyNames(user) // get all properties from instance

const reqBody = { 
  first_name: "afulsamet",
  username: "aful",
  email: "afulsamet@gmail.com",
  password: "333222233",
  title: "member", 
  age: 18 
} // user instance does not have title property

const model: Partial<User> = castByBody<User>(reqBody, userProperties);

console.log(model)

Output will be as in console

{
  age: 18
  email: "afulsamet@gmail.com"
  first_name: "afulsamet"
  password: "333222233"
  username: "aful"
}

TypeScript playground

1 Answers

When it's that way, it assignment non-members property of interface to my variable.

If you don't want that, then you can't using casting (it's not really casting, in TypeScript it's called type assertion). You do have to create a new object.

You can do that with destructuring as you have, although you can make it feel a bit less clunky by doing it sooner (in the parameter list), and you don't need a type assertion on it, TypeScript will see that what you're assigning is acceptable:

router.post("/post", ({body: { title, body }}, res: Response) => {
    const Model: Post = {
        title,
        body
    };
})

Doing that automatically (without having to type title and body) based on the type information would require TypeScript to provide runtime type information, which is currently outside TypeScript's goals (though it's a commonly-requested feature).

Of course, it doesn't have to be destructuring. You could have an array of your property names (the thing TypeScript won't give you), probably defined right alongside the type, and use that instead:

const PostKeys = ["title", "body"];

then

router.post("/post", ({body}, res: Response) => {
    const Model: {} as Post;
    for (const key of PostKeys) {
        Model[key] = body[key];
    }
})

perhaps even make it a reusable function

function createPost(source: any): Post {
    const post: {} as Post;
    for (const key of PostKeys) {
        post[key] = source[key];
    }
    return post;
}

then

router.post("/post", ({body}, res: Response) => {
    const Model: createPost(body);
})
Related