Angular frontend with REST backend: entity design for CRUD-operations

Viewed 163

I'm using an Angular 12 frontend, Spring REST backend for a Single Page Application.

What is the best way to create entities in Angular if the fields of an entity are different for every CRUD operation?

Let's take an User-object for example with different fields for every request type:

export interface UserGet {
   id: string;
   firstName: string;
   lastName: string;
   hobbies: Hobby[];
}

export interface UserPost {
   firstName: string;
   lastName: string;
   hobbies: number[];
}

export interface UserPut {
   id: string;
   firstName: string;
   lastName: string;
   hobbies: number[];
}

export interface Hobby {
   id: string;
   hobby: string;
}

As you can see, Id can be optional dependant on the request, and the field type of hobby can either be object or number.

Is it better to keep three objects in Angular like above, or should I create one "common" object like this:

Data of POST-Request:

export interface UserCrud {
   id?: string;
   firstName: string;
   lastName: string;
   hobbies: Hobby[] | number;
}

export interface Hobby {
   id: string;
   hobby: string;
}

What's the better approach on the long run with Angular 12?

Thx in advance

3 Answers

It's hard to answer questions like this without more background but the first solution seems like the obvious choice here for a couple of reasons,

  1. You are more clearly defining what information needs to be provided for each request type. Developers won't have to figure out what to put in each request.

  2. TypeScript will enforce that they are sending the correct data types with each request. With stuff like hobbies: Hobby[] | number; you are telling TypeScript that either type is valid for requests in general and that isn't true.

The way I would structure this, is not as much based on which HTTP methods these are used, but what they mean in context.

Based on this, I'm seeing two true entities:

  1. A user
  2. A request to make a user

Based on this, this would be the convention I would follow:

export type User {
   id: string;
   firstName: string;
   lastName: string;
   hobbies: Hobby[];
}

export type CreateUser = Omit<User, 'id'>;

In my opinion, UserGet, UserPost and UserPut are very similar, now you can do several things such as creating a service that generates the object and omits the fields you want but I would not do this Hobby[] | number on a contract (interface), is a sure way to get weird mutations to happen elsewhere in the application, I normally tend to do this on parameters, scoped variables or generic classes such as Subject<number | string> or something like that. I would use a generic interface as the base, basically:

export interface UserBase<TValue> { 
 id?: string;
 firstName: string;
 lastName: string;
 hobbies: TValue[];
}

then you can have separate interfaces for each:

export interface User implements UserBase<Hobby> {};

export interface UserRequest implements UserBase<number> {};

export interface Hobby {
  id: string;
  hobby: string;
}

Of course in their own respective files.

Also you can remove the the Http Verb from the interface name, interfaces/objects should be named for what they are and purpose the operation that makes use of it should not be included in as it can become quite confusing if used elsewhere in the application but for a different purpose, but that's my opinion.

Related