-2

I am currently trying a create a fairly simple, declarative RESTful CRUD framework, whereby I can define a resource type, and have both the API endpoint created, as well as the the functions to Create/Read/Update/Delete the declared resource.

ie. the usage would look a little like this:

interface Student {
     name: string; 
     dob: Date; 
}   

const {fetchFunctions, initApp} = createContract<Student>(app, "/students"); 

//Serverside: 
const app = express(); 
initApp(app); 

//Clientside     
fetchFunctions.post({   //OK. 
   name: "bob", 
   dob: "11/11/1911"
}); 

fetchFunctions.post({   //Type error, because dob isn't included. 
    name: "Bob"
});


const student = await fetchFunctions.get("111"); 
console.log(student.name); //OK. 
console.log(student.foo); //Type error, because Student doesn't have foo. 

Where I'm running into an issue is around the generation of IDs, and whether to include them in the resource object itself.

ie. Say I have done GET /students, to get a list of students, then I have to return the ID of the resources somewhere otherwise I am going to have no way to update them.

So either, I could put the ID on the resource:

[
   {
      id: "123", 
      name: "Bob", 
      dob: "11/11/1911", 
   }, 
   {
      id: "234", 
      name: "Alice", 
      dob: "12/12/1912", 
   }
]

Or on some 'Resource Wrappers'

[
   {
     id: "123", 
     data: {
       name: "Bob", 
       dob: "11/11/1911", 
     }
   }, 
   {
     id: "234", 
     data: {
       name: "Alice", 
       dob: "12/12/1912", 
     }
   }
]

(Is this even RESTful? , and really, it's not even different data).

The problem then, is I have to add the ID to the Student interface, and if I want to POST a student (without the ID), then I have to make it optional, and that starts getting messy, (ie. warnings about a possible null id everwhere).

Another solution is that I have two interfaces one Student and one NotYetCreatedStudent that doesn't have an ID, and I POST the latter instead. This seems kind of tidy in a sense - but I don't really like the ID of having to create two interfaces for every resource, having a whole second generic type on my functions etc.

This popular Stack Overflow question addresses the difference between POST and PUT, and basically the consensus seems to be that for creating a new resource, you do either:

POST /resource

or

PUT /resource/123

So maybe instead, then I could just not do 'POST without ID to create a resource' and instead do 'generate a UUID on the client side and PUT with ID' - which is seeming like the simplest solution.

Is there something I'm misunderstanding about RESTful APIs here that would help me solve this problem?

  • just generate the ids client side. youll need them if you manipulate more than one object at a time anyway – Ewan Jun 3 at 6:27
0

Say I have done GET /students, to get a list of students, then I have to return the ID of the resources somewhere otherwise I am going to have no way to update them.

Yes -- welcome to the world of hypermedia. More precisely, you need to return a link -- which means that your media types must clearly define how links are described in representations.

For example, in HTML we have a and img and script and so on that all have slightly different semantics.

The problem then, is I have to add the ID to the Student interface, and if I want to POST a student (without the ID), then I have to make it optional, and that starts getting messy, (ie. warnings about a possible null id everywhere).

Yes: it often makes sense for message schema to have optional fields, and that in turn means that the consumer of the message needs to be able to handle the optional-data-absent cases.

That doesn't necessarily mean that you need to go that way. Perhaps instead of submitting a Student, what the client sends is an EnrollmentRequest, which includes some of the student information but doesn't include bookkeeping metadata.

Is there something I'm misunderstanding about RESTful APIs here that would help me solve this problem?

A lot of people misunderstand REST, so it's likely.

Here's my summary: REST is the architectural style of the world wide web. So if you think through how you would design a web site that does the work, then you will have an understanding of what a RESTful design would be, and you can start thinking about alternative implementations.

  • Just read Roy Fielding's dissertation if you want to understand what REST really is. – Robert Harvey Jun 3 at 14:46
0

Ok, here's some clever Typescript that solves this:

interface HasId {
    id: string; 
}

type Omit<T extends HasId, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type OptionalId<T extends HasId> = Omit<T, "id"> | T; 

And now, when you are creating your function createContract, then you create it with the signature

function createContract<T extends HasId> {

}

And the post function will have the signature

interface PostFunction<T>{
    (data: OptionalId<T>) :Promise<T>; 
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.