1

I'm stuck trying to solve a problem. I'm using express js to build a rest api. I want the user to be able to update their profile.

I've created a User model:

export type User = {
    email: string
    creation_date: number
    first_name?: string
    last_name?: string
    payment_detals?: {
        iban: string
        last_updated: string
    }
    address?: {
        city: string
        street: string
        house_number: string
        postal_code: string
    }
    products?: string[]
}

But I want to receive the request body and update the value for that user in the database (No SQL, Firebase). But I don't want the user to add fields which are not specified in the User type.

How do I check if the request body has type User, if not throw an error?

The route:

const edit = async (req: Request, res: Response) => {
    try {
        let data = req.body
        if (data instanceof User)
    } catch (err) {
        return res.status(501).json({ error: err.message })
    }
    return res.status(200).json({ status: 'ok' })

I can't find any help on the internet, so maybe someone could help me out?

So for example, if the payload of the post request is:

{
  "name": "Jack"
}

It should throw an error, because name is not a member of User.

How can I solve this? All help is appreciated!


Updated now trying with classes:

export class CUser {
    email: string
    creation_date: number
    first_name?: string
    last_name?: string
    payment_detals?: {
        iban: string
        last_updated: string
    }
    address?: {
        city: string
        street: string
        house_number: string
        postal_code: string
    }
    products?: string[]
}

The route

const edit = async (req: Request, res: Response) => {
    let data = req.body
    console.log(data instanceof CUser)
    return res.status(200).json({ status: 'ok' })
}

When the request.body is:

{
    "email": "mike@gmail.com",
    "creation_date": 849349388935
}

The data instanceof CUser will always result to false. Wait is it maybe because data is an object?..

Steven Soekha
  • 721
  • 1
  • 9
  • 20
  • You have to understand that TypeScript is compiled to normal JavaScript, which means all typehinting is stripped off. This means that the JavaScript that will be left, does not check for a class values to match with your input. You really have to create custom validation to make sure the object properties match your input. – PIDZB Oct 10 '20 at 20:32
  • Thanks for your reply. Yes I understand how it works now, but how can I tackle this problem? To deny unwanted fields? – Steven Soekha Oct 10 '20 at 21:01
  • You can use instanceof when you create a Class instead of a Type if im correct. Then you just do if(!data instanceof User) { throw new Error('Not a User');} – PIDZB Oct 10 '20 at 21:04
  • But I see in a different comment that you cannot use Class, so that wont work as well... – PIDZB Oct 10 '20 at 21:10
  • The only solution then is very ugly... You have to validate all fields in the object, e.g. for(let k in data), and check if it matches all properties in the Type... – PIDZB Oct 10 '20 at 21:11
  • Yeah I was about to say that. I've created a class User but instanceof always results to false... Damn.. – Steven Soekha Oct 10 '20 at 21:11
  • Hmm, that is strange. I will try to create a snippet that works for u... Gimme a sec – PIDZB Oct 10 '20 at 21:12
  • Yeah I also created the for loop to check each key specific, but that would also not work. I could have done it the wrong way. Maybe you can show me a snippet of how you would do it, that would be very helpful. – Steven Soekha Oct 10 '20 at 21:14
  • Ok, I've played around with type a bit. A type isn't even compiled to Javascript to begin with. So you never can validate that with instanceof. – PIDZB Oct 10 '20 at 21:18
  • Could you maybe check my post, I've updated it. I'm now trying to do it with classes. What am I doing wrong? – Steven Soekha Oct 10 '20 at 21:20
  • Ok, Im astounded by the fact that I cannot get this to work. Apparently TypeScript strips away all properties that are not defined in a constructor, which completely breaks the instanceof validation. – PIDZB Oct 10 '20 at 21:32
  • Yeah it's not fun that I'm forced to use the constructor. But I've disabled it in the tsconfig.json with: "strictPropertyInitialization": false. Then I can skip the constructor. But it seems that if I want to use the `instanceof` method the data should be initialized as an object... – Steven Soekha Oct 10 '20 at 21:35
  • Maybe use an input lib, as mentioned here: https://blog.logrocket.com/dynamic-type-validation-in-typescript/ – PIDZB Oct 11 '20 at 08:53

3 Answers3

2

Types or interfaces that you define in Typescript are stripped when it's converted into Javascript, so you won't be able to able to check the type during runtime.

What you'll need to do is create a type-guard function that asserts true or false whether or not your request has those specific User properties.

For a good example see: How to check the object type on runtime in TypeScript?

dwosk
  • 1,202
  • 8
  • 11
  • 1
    I'm now reading this: https://basarat.gitbook.io/typescript/type-system/typeguard , and I do understand what you're explaining. But this only checks if a member is present, but it does not check the members which should not be present. So I can create a function that checks return obj.email !== undefined . But I want to check unwanted fields. If the user now sends a field `age` it will pass my code and save it into the database. I appreciate your answer. Maybe you have another idea? Something I might be missing. Please do correct me if my understandings are wrong. – Steven Soekha Oct 10 '20 at 20:59
  • Yeah, there isn't a super great way to do it. It's up to you how in-depth you want to make your type-guard. It's ugly but you could look through this: https://stackoverflow.com/questions/49580725/is-it-possible-to-restrict-typescript-object-to-contain-only-properties-defined – dwosk Oct 10 '20 at 22:33
1

You can create a constructor or function in a typescript class , which will take the req.body and only pick the required keys from the object, assign to this member variable and return you a new instance of the User object.

Now you can apply the checks on User instance or also can create a validateObject method inside the User class

Manish Kumar
  • 101
  • 11
1

I've solved this by writing a function which compares the request body with the types that I expect, and it works great! If there is something wrong with the fields or the required is wrong, the server will throw an error immediately. Here are some code snippets:

The functions

export type Schema = {
    fields: { [key: string]: string }
    required?: string[]
}

const required = (obj: any, required: string[]) => {
    for (let key of required) {
        if (obj[key] === undefined) return false
    }
    return true
}

export const validate = async (obj: any, model: Schema) => {
    if (model.required) {
        const status = required(obj, model.required)
        if (!status) return false
    }

    for (let key of Object.keys(obj)) {
        if (model.fields[key] === undefined) return false
        else if (typeof obj[key] !== model.fields[key]) return false
    }
    return true
}

Example type

import { Schema } from './'

export type User = {
    email: string
    creation_date: number
    subscription: string
    first_name?: string
    last_name?: string
    payment_detals?: {
        iban: string
        last_updated: string
    }
    address?: {
        city: string
        street: string
        house_number: string
        postal_code: string
    }
    categories?: string[]
    products?: {
        ean: number
        category?: string
        notes?: string
    }[]
}

export const UserSchema: Schema = {
    fields: {
        email: 'string',
        subscription: 'string',
        first_name: 'string',
        last_name: 'string',
        payment_details: 'object',
        address: 'object',
        categories: 'object',
        products: 'object',
    },
    required: ['email']
}

On the server

let status = await validate(body, UserSchema)
if (!status) return res.status(422).json(Message.error.wrong_request_body)

// else continue
Steven Soekha
  • 721
  • 1
  • 9
  • 20