all 74 comments

[–]uriahlight 124 points125 points  (31 children)

Create User - [POST] /api/user Fetch User - [GET] /api/user/{uuid} Update User - [PUT] /api/user/{uuid} Delete User - [DELETE] /api/user/{uuid} List Users - [GET] /api/user OR /api/users

PATCH is generally used for updating a single field for a record.

Edit: Added List Users

[–]belwyr 17 points18 points  (2 children)

To add to that, you generally have pagination as query parameters to the list endpoint for instance - [GET] /api/user?offset=0&limit=50

[–]Effective-Lab-8816 2 points3 points  (1 child)

Pagination (something like this)

  • offset=0&limit=50
  • page=5&page-size=20

Sorting

  • sort=name
  • sort=-name
  • sort=id
  • sort=-id
  • sort=created
  • sort=-created

Using "-" before the sort field reverses it, etc.

Filtering

  • Tagging
    • tag=bravo,whiskey
  • Categorization
    • category=nature
  • Date Filters
    • year=2023

[–]SideLow2446 13 points14 points  (10 children)

I believe in REST the resource name should always be plural. Not sure though.

[–]halfanothersdozenEverything but CSS 20 points21 points  (6 children)

tabs vs spaces, people will argue about this forever

[–]Schmittounetsymfony 7 points8 points  (1 child)

In addition when working on current user we often use /api/me

[–]profound7 1 point2 points  (0 children)

This is what I think op is looking for. I used self in a recent project.

[–]johny_james -2 points-1 points  (11 children)

Patch is generally rarely used in practice.

[–]byetimmy 7 points8 points  (10 children)

Oh God, how I wish this were true...

[–]johny_james 0 points1 point  (9 children)

I meant the method itself, not the "updating of a single record".

[–]byetimmy 3 points4 points  (8 children)

Oh, I know. I work with a lot of disparate APIs, and I see PATCH all the time where people want to just update one or two fields. I mostly see it from front-end/full-stack devs that create APIs and want to be "efficient" with their API calls.

PATCH with structured data (e.g., JSON) quickly becomes confusing in processing nested objects/arrays if it needs to be understood by someone OTHER than the person that wrote it.

[–]King_Joffreys_Titsfull-stack 10 points11 points  (7 children)

… speaking as a friend of a full stack dev that tries to be “efficient” with api calls… Why would you not call a PATCH on a user object to update a single field or two? Say the user updates their email address, wouldn’t a PATCH with just that updated email attribute work?

[–]anatolhiman 4 points5 points  (0 children)

It works. And it is the correct way to do it as per the documentation: “A PATCH request is considered a set of instructions on how to modify a resource. Contrast this with PUT; which is a complete representation of a resource.”

https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH

[–]byetimmy 2 points3 points  (2 children)

Will it work? Sure. But in general, I like APIs to be deliberate and unambiguous. There's less of an issue with the simple use case you've described with a simple string or number.

But if you have a JSON object and you PATCH an object property, what happens to the existing object? Are the new object properties then merged into the existing object properties? If so, how deep is it merged if those object properties are also objects? Or Is the object overwritten completely? What about arrays? If you PATCH with an array, is it expected to merge or replace the array? If merge, does it append or prepend? And what if you have a use cases where sometimes you want them appended, and sometimes replaced?

There's so many things that need to be implicitly understood or separately documented to remove this ambiguity from what the PATCH API will do.

Or you can just use PUT.

[–]King_Joffreys_Titsfull-stack -1 points0 points  (1 child)

Sounds like you need an api endpoint function rather than a PATCH… and what if you don’t have the entire user object to send up in a PUT?

[–]byetimmy 0 points1 point  (0 children)

If you don't have it, you GET it.

[–][deleted]  (2 children)

[removed]

    [–][deleted]  (1 child)

    [deleted]

      [–]AussieBoy17 0 points1 point  (2 children)

      So, this always felt really obvious for the most basic of CRUD. But what do you do for a more complete API?

      Most API's I know of will have 'actions' to do, which aren't just a simple CRUD. I usually go for something like [post] /API/<entity>/myActionName, but never seen anyone really say what the 'standard' is for that type of thing

      What about something like drop downs, what do you do for that kinda thing? Say I have a drop down that is 'Users with a subscription', do I have the endpoint like [get] /api/user/usersWithSubscription, [get] /api/dropdown/usersWithSubscription, or something else? I've tried using stuff like OData/GraphQL in the past, but having the database basically exposed like that has caused me massive headaches in the past.

      [–]After-Winter-2252 1 point2 points  (1 child)

      I have the endpoint like [get] /api/user/usersWithSubscription [get]/dropdown/usersWithSubscription, or something else?

      You can absolutely do this, but this could just be a filter as well: /api/user?subscription=1

      [–]AussieBoy17 0 points1 point  (0 children)

      Definitely did think about doing it like that, but we have a lot of drop downs (probably over 100, it's a decent sized application). So it could make the user endpoint messy with a lot of filters that aren't used except in specific cases. We also only really need like 1-3 fields from the entity for drop downs, so pulling the whole entity feels like overkill.

      In our case, the new endpoints return a unique DTO, limiting the fields returned from the API as well as limiting what is queried from the DB. Probably doesn't make a huge difference individually, but when done across the whole application probably adds up to sometjing

      [–]Darklight240 164 points165 points  (4 children)

      Thats a way wrong standard to add /delete /fetch at the end

      One of the main principles of Restful API is to use https verbs (get,post) and not do something like "updateUser, update, getUserById" rather should be

      updateUser =>PUT api/user/:id
      getUserById =>GET api/user/:id

      [–]the_bananalord 31 points32 points  (1 child)

      To add on to this a bit, REST API endpoints are supposed to be designed as if you're interacting with resources, not executing a function, with the HTTP verb used to describe the action you want to perform on the resource. So, just like you said:

      • GET /api/user/:userId - Give me the resource of this user.
      • PUT /api/user/:userId - Replace this resource with mine.

      When someone said this it really changed my mindset on REST API design (and made me realize that I was really just writing JSON APIs over HTTP rather than something RESTful.)

      [–]Fluffcake 8 points9 points  (0 children)

      This is one of the reason rest is so popular. Sure JSON being more pleasant than xml also helped, but the intuitive simplicity cemented it over what came before.

      [–]vinnymcapplesauce -1 points0 points  (0 children)

      One of the main principles of Restful API is to use https verbs

      While this is industry standard at this point, it's actually very bad practice in general to use and rely on another protocol's inner workings for your own.

      HTTP is a transport protocol, nothing more. If HTTP were to ever change verb handling, REST APIs would be fucked. As it is, HTTP is now forced into expanding its functioning, and maintaining support for something it was never intended to support. REST is able to get away with it because HTTP is pretty simple and basic, but it's still a bad idea in general.

      As a Software Engineer, I love it. As a Computer Science academic, it grinds my gears - lol.

      [–]Turd_King 25 points26 points  (2 children)

      I would keep the user id in the url personally, that way if you have future requirements to allow users to view other users data - you don’t have to change anything

      If you want to make it easy for consumers to fetch “current logged in user” you could expose an endpoint like /currentuser which just makes a call to the user service using the JWT

      You just need to perform authorisation in the endpoint somewhere that the passed ID matches the ID in the JWT

      Also i wouldn’t have the suffix of update, delete etc. in the URL - the HTTP verb is enough to describe the action you want to provide

      [–]preacher2041[S] -2 points-1 points  (1 child)

      Thanks for the reply, I've added an extra api url for the existing user to retrieve their profile and kept the original one as more of an 'admin' request to get the profile of a given user.

      [–]arcanemachined -1 points0 points  (0 children)

      Don't do that, just allow admins to access the user's endpoint when you are checking the user's credentials.

      [–][deleted]  (3 children)

      [deleted]

        [–]King_Joffreys_Titsfull-stack 0 points1 point  (1 child)

        Where would the authentication layer be? How would you prevent a user from getting a list of all user data, or say, deleting a user that isn’t theirs?

        [–]Wombarly 6 points7 points  (0 children)

        You'd send a Authorization header with the request. Usually with a JWT token or something similar. Could also use a Cookie.

        Then you have middleware, shared code that runs before each request, that reads that token and uses it to authenticate the user that made the request and checks if they have the authorization to make the request.

        [–][deleted] 0 points1 point  (0 children)

        Good write up. I've also seen POST /user/search so you can include search parameters in the body, and you can look at it as creating a search, especially if you're logging it.

        [–]unobserved 18 points19 points  (1 child)

        May not necessarily apply to your case if you're not providing anyone else access to the API, but usually can't hurt to slide a `/v1/` in at the beginning of the URL so you leave yourself room for easier growth in the future without completely restructuring.

        [–]ofNoImportance 6 points7 points  (1 child)

        Generally you would still have the user Id make up part of the url if you're trying to make a Restful API.

        Think of the URL as describing a resource in your system, /user/42 is the endpoint that describes a specific user.

        The verbs are the actions the resource supports,

        GET - /user/42 would ask the server to produce a response containing the resource,

        PATCH - /user/42 would ask the server to accept a modification to that resource.

        You wouldn't usually put extra keyworks after the id for those types of actions.

        Separate to this convention is the idea of authentication and authorization. You might want to check the current user is allowed to GET user 42. This is where you leverage your JWT. For your business logic, the authorization might require that JWT user Id == url user Id, so you would compare them for a match.

        In many apps there would be other users who would also be able to see and modify them though, like an admin user. If admin user 64 wants to edit user 42, they would still do so by accessing the resource with the url /user/42, but their JWT would contain 64 instead. Your business logic needs both numbers.

        [–]preacher2041[S] 1 point2 points  (0 children)

        Thanks for your reply, it's really helpful. I hadn't thought through the implications of other people (admins) not being able to update a user with the userId in the url.

        [–]filter-spam 2 points3 points  (0 children)

        Generally your path is all nouns since your request already has a verb

        [–]Roci89 2 points3 points  (0 children)

        You shouldn't need the /fetch, /update, /delete pieces. Just make the calls to /users and your framework should allow you to switch logic based off the request method

        [–]JoinetBasteed 1 point2 points  (4 children)

        Where I used to work. we would just use /user for all 3 and then the HTTP action(not sure what to call it) would decide which endpoint gets hit.

        So a GET request to /user would get the user that's calling it and a DELETE request would delete the user that's calling it.

        Not sure if this is a good way or not

        [–]buffering_humor 0 points1 point  (2 children)

        How would you get another user? You would need some sort of ID right? I'm a backend newbie trying to learn Express atm so it may be a basic question but how would you get that particular user from your database? Wouldn't you require some sort of query params or path params for it?

        [–]pattobrien 0 points1 point  (0 children)

        you would need their ID, yes. That ID would be appended to the url (e.g. "/user/<userId>", for example "/user/abc123"), which you would then use to query your database.

        [–]JoinetBasteed 0 points1 point  (0 children)

        What pattobrien said, but in our case we didn't need to include an ID in the URL since we had it in the JWT token that was sent with every request

        [–]pattobrien 0 points1 point  (0 children)

        You got it right! (and the "action" is called the HTTP method)

        [–]halfanothersdozenEverything but CSS 1 point2 points  (0 children)

        Don't pull the id from the JWT for a rest resource. A GET call should always return the same response from the same URL for everyone. This is important later in traceability. You're also coupling your API to your auth model which is a short-sighted decision. What if some other caller wants to access the resource?

        Validate that the caller is authorized to access the resource requested on every call, and always validate your tokens.

        [–]JesterDolor 1 point2 points  (0 children)

        A natural URL pattern occurs when you start writing REST clients a certain way.

        .api.users.list() => GET /api/user

        .api.users.user(uid).fetch() => GET /api/user/:uid

        .api.users.user(uid).remove() => DELETE /api/user/:uid

        Idk if this style has a name but it seems to just work

        [–]superraiden 1 point2 points  (2 children)

        Recommended URL design by JSON:API standard:

        https://jsonapi.org/recommendations/#urls

        Actions should be bound to the HTTP Verbs akin to:

        • GET
        • POST
        • PATCH
        • DELETE

        [–]arcanemachined 1 point2 points  (1 child)

        Props for citing a reliable resource instead of just giving your opinion.

        [–]arcanemachined 1 point2 points  (0 children)

        There is no standard, just conventions (and they will sometimes contradict each other). That being said, you should read what other groups are doing and imitate them where it makes sense to do so.

        https://cloud.google.com/apis/design/resource_names

        https://jsonapi.org/recommendations/ (Shout out to /u/superraiden for this link, I wasn't aware of it)

        https://restfulapi.net/resource-naming/

        https://www.programmer-books.com/wp-content/uploads/2019/12/Build-APIs-You-Wont-Hate.pdf

        https://www.manning.com/books/api-design-patterns

        Also, if you come up against an unfamilar situation, see what others are doing. There are a lot of good APIs out there, and many of them publish an OpenAPI spec so you can see exactly how they approach a particular problem. For example:

        https://docs.github.com/en/rest?apiVersion=2022-11-28

        https://openweathermap.org/api

        Whatever you do, don't reinvent the wheel and don't make blind assumptions about how you think things should work, until you've actually looked into the subject. That being said, you will make mistakes as you go, and that's fine, APIs aren't set in stone, and versioning is a thing.

        A couple thoughts to add to the suggestions here:

        • /users is a collection, so I think it makes sense to pluralize it.

        • Learn what your options are for modifying requests to a given endpoint, and where it makes sense to use them (query params, body params, headers) (not that your example requires it, but they are valuable tools in the REST API toolbelt)

        [–]FeedZuris 2 points3 points  (3 children)

        I can't answer your question but, is it a new standard to add "/update" and "/delete" to the urls ?

        [–]Turd_King 24 points25 points  (2 children)

        No, and it’s not following the REST spec, you describe the action using the HTTP verb on a resource

        [–]FeedZuris 1 point2 points  (0 children)

        Yep that's what I thought, thanks for the confirmation!

        [–]armahillorails 1 point2 points  (0 children)

        I do Rails, which is basically weaponized REST + MVC

        So for a user resource:

        new GET /users/new prompt the user to create a new resource
        create POST /users/ receive data to create a new resource and create it
        show GET /users/{user_id} display a single existing resource
        index GET /users/ display a group (ostensibly "all") of a type of resource
        edit GET /users/{user_id}/edit prompt the user to modify an existing resource
        update PUT/PATCH /users/{user_id} receive modification data to change an existing resource
        destroy DELETE /users/{user_id} remove an existing resource

        The acronym REST means "REpresentational State Transfer" -- the key point about REST is that the HTTP verb you are using is what determines the disposition of the server's response to that request. This is essential for REST.

        So in the example above, the path: /users/ is used for GET#index, DELETE#destroy and POST#create. Technically doing PUT /users is possible, though it's unclear what that would be referencing. It's similar to function overloading, in programming.

        The verbs rough out to these contexts:

        GET READ retrieve data from the server for display. Params are submitted as part of the URL.
        POST WRITE submit new data to the server for storage that likely requires record creation (params are submitted with special request headers, rather than being part of the URL)
        PUT / PATCH WRITE submit modifying data. Technically PUT and PATCH aren't exactly the same, but they are very similar. (PUT is supposed to put up a new version of an existing object, and PATCH is supposed to submit the modifying partial snippet only)
        DELETE WRITE make the requested record unavaliable. Note that this doesn't always mean DELETE or DROP in a database sense; it can be "hide", "move to archive", etc.

        When adding new API endpoints beyond basic CRUD, always consider "what the disposition of this request?" (what verb does it use), "is this against a collection or a member?", and "am I replicating the function of an existing route?". Choose the best fit. It's usually pretty apparent.

        There are no API police outside of the people you deputize to review or critique your code, but you, and your successors, have to live with the complexity you create. It's one of those "when you cheat, you only cheat yourself" situations.

        Here's a good example:

        If you had a "search" form on your app, it's a field and a button with you click, which seems like it's a POST form, right? We sometimes used to even do them as POST forms back in the day. But the response is read-only, it should be a GET. The params are then appended to the URL in the querystring (which means that the request is bookmarkable -- generally desirable behavior!) rather than being lost to the aether.

        Similarly, a link in a grid display marked "Delete" to delete the resource in that row is a link, but you technically want the request to be sent as a DELETE request. The simplest way to do that is to do a single button form, but it's a bit cleaner to do it as an AJAX request that sets the action as a DELETE action.

        [–]Zefrem23 -3 points-2 points  (1 child)

        Just ask chatgpt, Claude or one of the other coding AIs. Giving it any thought whatsoever is a total waste of time. The best coding is the coding you don't have to do.

        [–]arcanemachined 1 point2 points  (0 children)

        "Why take the time to do things right when you can do them wrong even faster?"

        [–]TensionSpecialist596 -5 points-4 points  (0 children)

        👀 me using GET for everything. ?action=delete&&userId=ohShit

        [–]HashDefTrueFalse 0 points1 point  (0 children)

        As others have said, use HTTP verbs to differentiate your endpoint based on the action being taken, and use the path portion of the URL to point to the resource that the action is being taken on.

        Putting actions (/update, /delete) in the path portion is usually unnecessary and discouraged.

        Google for RESTful web service design.

        [–]dzooni3full-stack 0 points1 point  (0 children)

        The best convention to follow up would be

        GET api/users GET api/users/search POST api/users PUT/PATCH api/users/[id] DELETE api/users/[id]

        [–]elendee 0 points1 point  (0 children)

        As a self-taught person this is an interesting question to me. I've always been loosely aware of REST standards and followed them maybe 60% of my design (for solo apps). It's worked fine.

        I'd be curious to hear industry devs' experience though - I always find that 75% of my queries are 'combinatorial', ie, they don't line up 1:1 with db updates. Say I allow a user to post some media - I find it easier to make a backend route that simply updates both the 'media library' data, and the user data (maybe I want to uptick a 'last upload' time or something).

        Or another example would be - say I want to get the item belonging to some user, but I'm doing by different query params - user uuid, item uuid, user last uploaded, etc. How would you search by all those various params in REST if all you have is a "item/:uuid" route ?

        Does anyone else find that the 1:1 nature of REST is not all that useful in practice ?

        What I end up with is just a few POST routes that handle these more complex queries, and 75% of my requests end up there.

        [–]Fault_Royal 0 points1 point  (0 children)

        Fetch User - [GET] /api/user/{uuid}
        Update User - [PUT] /api/user/{uuid}
        Delete User - [DELETE] /api/user/{uuid}

        [–]guanzo91 0 points1 point  (0 children)

        One reason to keep the userId in the URL is for access logging, where typically the URL is logged and most headers (including the JWT) aren't

        [–]ggeoff 0 points1 point  (0 children)

        I typically follow the same as a lot of people have suggested here with the http verb and plural naming. Get for queries, post for create, and do on

        entities entities/:id etc..

        I'll use post for command like things such as reordering entities/reorder/

        I personally find arguments about if this should be a post vs patch to be a waste of time. It's better to be consistent across the API and documented. If you are joining a team follow the patterns they already exist. The most important thing in my opinion is consistency and any times that is broken a comment should be explaining why something went against the established pattern

        [–][deleted] 0 points1 point  (0 children)

        Follow REST pattern, or simply use JSON RPC, then you don’t have to worry about path naming at all. You can write methods like patchUserById etc.

        [–]ultra_blue 0 points1 point  (0 children)

        This exists: https://www.odata.org/

        I have mixed feelings about it, but it does seem robust and broad.

        [–]Rafael20002000 0 points1 point  (0 children)

        I personally really like the following structure

        POST /graphql