#general way to approach user based rest api's

53 messages · Page 1 of 1 (latest)

soft parcel
#

I come from a python background so for my user registration api's i would make classes that inherit from pyadntic.BaseModel which define my incomming json payload and out going too. In go i was reading code on the internet to see the best practices and I am a bit confused now, so is it okay that if i have a user model like

type User struct {
  ID        int64
  Username  string
  Email     string
  Password  string
  CreatedAt time.Time
  UpdatedAt time.Time
}

this which represents a user in the database,

so for my user register route should i have two more structs just like python something like

type UserCreateParams struct {
  UserName string `json:"user_name"`
  Email    string `json:"email" validate:"email"`
  Password string `json:"password" validate:"gte=8"`
}

type UserCreateResponse struct {
  ID        int64     `json:"id"`
  Username  string    `json:"username"`
  Email     string    `json:"email"`
  CreatedAt time.Time `json:"created_at"`
  UpdatedAt time.Time `json:"updated_at"`
}

or are there any other ways for a general user oriented backend for modern day applications?

soft parcel
#

bump

south jay
#

Having multiple DTO types is fine, but you end up having to map between your types a lot, which is a good opportunity for human error

#

If I'm writing a JSON API, I have no problem with having the json tags be on the domain type, as they come in handy for debugging also. If you're worried about sensitive info being leaked, you can omit those fields from marshalling with json:"-"

soft parcel
south jay
#

Yeah, this is the problem with struct tag-based validation. Or anything that relies on struct tags.

soft parcel
#

ya so i am forced to have atleast two structs one for incomming data and one for outgoing

south jay
#

It's not bad practice. If anything it's good practice. I just find it a bit of a chore to maintain all these types that are virtually the same

soft parcel
soft parcel
south jay
#
type User struct {
  ID        int64
  Username  string
  Email     string
  Password  string `json:"-"`
  CreatedAt time.Time
  UpdatedAt time.Time
}
soft parcel
#

and for incomming json data of the user?

#

that would have password

south jay
#

Let me check that

soft parcel
#

it didnt for me i guess, if i did everything correctly

#

also i think it wont be able to anyways because it doesnt know password is the key to look for if we dont say it in the json

#

i did have the problem so i searched and all and i just got consufed everyone is doing all kinds of stuff

south jay
#

Yeah, you're right. It completely ignores it

#

What you have is fine, with separate DTOs

#

It keeps a nice separation between your domain types and their use cases

#

You can also cut down on some of the mapping work by embedding your main type

soft parcel
#

thnx, ill have one to represent the model in the database in the models folder and the file where i define the create user handler ill have those

type UserCreateParams struct {
  UserName string `json:"user_name"`
  Email    string `json:"email" validate:"email"`
  Password string `json:"password" validate:"gte=8"`
}

type UserCreateResponse struct {
  ID        int64     `json:"id"`
  Username  string    `json:"username"`
  Email     string    `json:"email"`
  CreatedAt time.Time `json:"created_at"`
  UpdatedAt time.Time `json:"updated_at"`
}
south jay
#
type User struct {
    ID        int64     `json:"id"`
    Username  string    `json:"username"`
    Email     string    `json:"email"`
    Password  string    `json:"-"`
    CreatedAt time.Time `json:"createdAt"`
    UpdatedAt time.Time `json:"updatedAt"`
}

type CreateUser struct {
    User
    Password string `json:"password"`
}
#

And if the client specifies read-only fields like createdAt, or updatedAt, I'd just ignore them

soft parcel
south jay
#

So long as you don't plan on using UserCreateParams elsewhere i.e. not in a http handler

#

A lot of the time, you want types like that to be independent of the use case

#

Say you were writing a gRPC adapter, or CLI application for mutating users, you'd want to reuse the same types

soft parcel
# south jay That's fine too

so do i embed the models.User into my UserCreateParams and UserCreateResponse in the file where i have the createUser handler or do i define those also in the models only , i am asking since models usually just refers to the database models and there is typically schemas folder for what i am doing

south jay
soft parcel
soft parcel
#

also about that validator package, is it preferable over the go-playground one?

#

or like any is okay

south jay
#

That's subjective. Give it a try and see why you prefer

#

I tend to avoid anything that's based on struct tags, except for basic encoding/decoding things like json/xml/csv. You lose all type safety by encoding your validation rules in struct tag strings. It's brittle.

soft parcel
#

fair point

south jay
#

Meaning, you won't know that you've made a typo in your validation rule until runtime

#

And even then, you might not even encounter it during manual testing. So it'll break prod 😄

soft parcel
#

the struct tags were weird to me too since i come from python, ts ecosystem

south jay
#

Yeah, I'm not a huge fan of them tbh. The language could use with a more versatile meta annotation system

soft parcel
#

i have heard that the maintainers dont listen that much to others and just do what they feel is the best

#

but anyways thanks for your help

#

i have any more questions regarding api stuff, ill keep this thread open for that, that ok?

south jay
south jay
#

Threads are searchable, so it's best to keep them as concise as possible, and not stray to far from the initial topic. Better for other users if they have similar problems