DEV Community

official_dulin
official_dulin

Posted on • Edited on

How would you name the TS interface like this(TS types naming convention)?

For the API /v1/users, the data shape of the user is:

{
  id: 1,
  name: 'teresa teng'
}
Enter fullscreen mode Exit fullscreen mode

For the API /v1/user/:id, the data shape of the user has some extra fields:

{
  id: 1,
  name: 'teresa teng',
  role: 'admin',
  email: 'example@gmail.com'
}
Enter fullscreen mode Exit fullscreen mode

I'm looking for good names for the interfaces. The names I've used are:

Option 1:

interface BaseUserEntity {
  id: number;
  name: string; 
}
interface UserEntity extends BaseUserEntity {
  role: string;
  email: string;
}
Enter fullscreen mode Exit fullscreen mode

I don't really like Base prefix for the interface name.

Option 2:

interface UserEntity {
  id: number;
  name: string; 
}
interface UserDetailEntity extends UserEntity {
  role: string;
  email: string;
}
Enter fullscreen mode Exit fullscreen mode

I think User and UserDetail are the same thing, introducing excess words - Detail. Do you have any suggestions for the naming convension? Thanks.

Top comments (6)

Collapse
 
bias profile image
Tobias Nickel

I have an other thought on the topic, And I think this is an approach that is promoted/adopted by the golang community(correct me if i am wrong).

What about having only one global (maybe in a types directory) user type/interface and other versions with only partial set of fields, they are only defined local in the module that uses them.

Collapse
 
mrdulin profile image
official_dulin • Edited

I have this thought as well. Ideally, we should have interfaces corresponding to the data model represented by the database tables, and I remember that there are ORM and Code Generation tools that can do this.

In addition, for the front end, we should define the types and interfaces for the view model.

The model(type, interface) conversion process is as follows:

API => Frontend representation: Domain Model Interface => View model interface
Frontend representation => API: View model interface => Domain Model interface.

We will convert the domain model interface that contain all fields to target types through TS's Utility Types or Map Type rather than repeated definitions.

The naming convention for the derived types just like my above comment. The key point of the naming convention should be readability, scalability, reusability and maintainability.

More naming convention examples:

type UserEntity {
  id: string;
  name: string;
  role: string;
  email: string;
}
// Comsumer side
// For react hook
type UseGetUserQueryPayload = {
  id: Pick<UserEntity, 'id'>
}
const useGetUserQuery = (payload: UseGetUserQueryPayload) => {}

// For API service methods
type GetUsersResult = Pick<UserEntity, 'id' | 'name'>;
const getUsers = (): Promise<GetUsersResult> => fetch(/.../)

// But what name should be use for below situation
type UserOfProduct = Pick<UserEntity, 'id' | 'name'>;
type Product = {
  id: string;
  name: string;
  // I think it's not prefect. 
  // UserOfProduct is same with GetUsersResult, duplicated!
  user: UserOfProduct;   
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sirseanofloxley profile image
Sean Allin Newell

The previous advice given is great, here is an alternative way to model your domain:

/** Base entity attributes everything has */
interface Identifiable {
  /** The PK ID of this entity */
  id: number;
}

/** Anything that has access controls */
interface Permissable {
  /** The name of the role this permissable entity (ie a user) is assuming */
  role: string;
}

/** Anything that has an email address */
interface Emailable {
  /** The email address */
  email: string;
}

interface Nameable {
  name: string;
}

type User = Identifiable & Nameable;
type UserDetails = User & Emailable & Permissable;

const user: User = {
  id: 5,
  name: "Hector"
};
const userDetails: UserDetails = {
  ...user,
  email: "test@test.co",
  role: "super"
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
izio38 profile image
izio38

That's a very good question.

Today you have two versions of your User interface and tomorrow you'll have 3 then 4. It's not scalable this way. It's ok if the number of interfaces representing a part of an Entity is small (I would say 2 max).

In your case, I would pick 'DetailedUser', 'DecoratedUser' etc.. for the second interface. But for the first, it's not an User Entity, because it's lacking of some data. It would have been ok if the UserEntity interface defines all the properties a User has.

For such issues, for the sake of my applications, I prefer to not defined multiple interface and stay with only 'full' interfaces version of my entities.
Then, every : -function, -component, -class, etc... take some input that they defines and output something that they defines.

For instance, if you have a function that take a user with its roles and only uses its name and its roles. The function shouldn't take the whole user, instead it should only take name and roles properties.

I've seen others using full user interface all the time (or full class instance), it's also ok, it really depends of what you think it's better in your case.

If you want, you can provide more context of the exiting app behind these interfaces so we can help you better!

Have a nice day,

aaaand, Happy Coding!

Collapse
 
mrdulin profile image
official_dulin • Edited

Thanks for your advice! The reason why I use Entity word is because the user data has a id field.

So, based on your advice. I can name the interface like this:

interface UserEntity {
  id: number;
  name: string; 
  role: string;
  email: string;
}
type T0 = Pick<UserEntity, 'id' | 'name'> // Used by API /v1/users
type T1 = UserEntity // Used by API /v1/user/:id

// In the future
type T2 = Pick<UserEntity, 'role'> //. Used by consumer A
// ...the same
Enter fullscreen mode Exit fullscreen mode

But if I don't create types like T0, T1 and T2 to hold the target type, I have to pick the fields repeatly in the consumer side. It violates the DRY principle. I am looking for a general naming convension for T0, T1 and T2 types. Maybe like this:

type GetUserByIdResult = UserEntity // Used by API /v1/user/:id
const getUserById = (id): Promise<GetUserByIdResult> => fetch(`/v1/user/${id}`);
type GetUsersResult = Pick<UserEntity, 'id' | 'name'>[]
const getUsers = (): Promise<GetUsersResult> => fetch(`/v1/users`);
Enter fullscreen mode Exit fullscreen mode

Or type UserFromGetUserById {} and type UserFromGetUsers {}, the name consists by User + From+ method name of the API service

Collapse
 
yw662 profile image
yw662 • Edited

I would do

const Roles = ['admin', ... ] as const
type Role = typeof Roles[number]
type BaseUser = { id: number, name: string, role: Role }
type Admin = BaseUser & { role: 'admin', email: string }
...
type User = Admin | ...

type UserList = Omit<BaseUser, 'role'>[]
// or type UserList = Pick<BaseUser, 'id' | 'name'>[]
Enter fullscreen mode Exit fullscreen mode

or,

type UserList = number[]
Enter fullscreen mode Exit fullscreen mode

Why would you include name, when you don't want to include rule ?