Bill Nourse
05/11/2025, 11:55 PMsknowlton
05/12/2025, 2:51 AM.route( '/invoice/:invoiceID-numeric/refund' )
.withAction( {
POST : 'refund'
} )
.toHandler( 'invoice' )
no reason to use views/.cfm files in an APIsknowlton
05/12/2025, 2:52 AM.route( '/eventSignup/:eventSignupID{36}' )
.withAction({
GET = 'getEventSignup',
PUT = "updateEventSignup",
} )
.toHandler( 'events' )
BK BK
05/12/2025, 10:15 AMsknowlton
05/12/2025, 11:29 AMuser
and sometimes with users
. The only thing gained by combining them under users
is the assumption that all interaction with /users
is the same 'thing' whereas the fact that /user
and /users
are semantically different could mean they represent entirely different objects because user != users
.
Being able to assume that 'all users
interactions relate to a single resource' is of limited value. Being able to assume that /user
is an interaction with one thing while /users
interacts with many `user`s is of greater value to our customers and our developers. We like REST precisely because it sometimes says 'here is the convention' and then gets out of the way and lets you do what makes sense.BK BK
05/12/2025, 1:33 PMusers
is different from user
, and that each has a different interaction. But that does not mean you should design them as two separate resources.
Using the collective or plural form to represent an API resource results in a smaller design with greater applicability. I think, that is why best-practice recommends it.
For a start, the collection (users
) actually includes the singular-noun case (user
), together with every possible behaviour and interaction associated with user
.
The URIs are as follows:
Users: yourDomain.com/users
One specific user, having ID 1234: yourDomain.com/users/1234
Best-practice also recommends that you should avoid the common practice of using a method, operation or verb as an endpoint, as in the example <http://yourDomain.com/invoice/:invoiceID/refund|yourDomain.com/invoice/:invoiceID/refund>.
The recommendation is to design it instead as
Refunds resource: <http://yourDomain.com/invoices/:invoiceID/refunds|yourDomain.com/invoices/:invoiceID/refunds>
A specific refund: <http://yourDomain.com/invoices/:invoiceID/refunds/:refundID|yourDomain.com/invoices/:invoiceID/refunds/:refundID>
sknowlton
05/12/2025, 1:37 PMBK BK
05/12/2025, 1:57 PMbeing able to tell at a glance whether an endpoint is an interaction with a thing or multiple things
Whereas, to me, an endpoint represents the current state of a resource, and is not an interaction.sknowlton
05/12/2025, 2:04 PMBK BK
05/12/2025, 3:22 PMdawesi
05/13/2025, 3:39 PM/users
with an id after it /users/29302
is clearly a single user. TBH I used to like the plural/singluar combo, but when performance is king, having one instead of two standards for naming makes it faster.
We poured over the decisions behind taffy (cfml rest api) and others and decided to go with plurals as that made more sense in larger app and performance wise cost less to convert things between singluar and plural at the router level.
like do you use /users/:id/address/:id
or is that now just /address/:id
as an endpoint or BOTH (one mapped as an alias to other in router
at the end of the day , you chose what you do and developers have to use it, not matter what they thing is right. there is no perfect principal set for rest apis, and no one way to do it, there is many patterns that claim to be the truth, all of them are valid in their own context.
personally with rewriting also we reroute the entire address from <http://bob.com/one/two/three|bob.com/one/two/three>
to index.cfm?_endpoint=/one/two/three
then use the router in our framework to split out the endpoint, verb and other url params to work out the combo in our router.
our framework router just scans dirs and looks at decorated functions with router attribs that tell it what verbs and paths that route to a function, that then builds an in memory store to use for route lookup. (kind of like bean method in taffy)BK BK
05/13/2025, 7:37 PMlike do you use /users/:id/address/:id or is that now just /address/:id as an endpoint or BOTH...
I would use neither. Instead, I would use:
/users/:id/addresses/:id
or /addresses/:id
There are a number of considerations I would then use to decide whether the addresses
resource should have its own endpoint or whether it should appear in the endpoint as a sub-resource of users
. Those considerations would come from an analysis of the resources that make up the API's domain.
The main such consideration would be the association between users
and addresses
. Is it composition, aggregation or just a loose association?
If it is composition, then I will definitely use /users/:id/addresses/:id
.
If it is aggregation, and addresses
does not feature on the right-hand side of any other has-a relationship then I will use /addresses/:id
.
That's just me. At the end of the day, you do you.Bill Nourse
05/13/2025, 11:03 PMdawesi
05/13/2025, 11:15 PM"cfscript based api router component with jwt integration, request throttling and decoration based routes making use to validate any input params based on function params and any annotations in function comments"
and you'd basically have something to get going in seconds. Then you could simply add api_route="/users/[:id]" and api_verbs="GET,POST,PUT" api_weight="100" after the function brackets in your code (or just create a static lookup table) and get then ask copilot to create a component to parse your cfc files recursively and generate a routing table. Basically you just created your own taffy ;-)
I did exactly this for an api i needed to spin up privately for a business that needed something quickly for a endpoint... the code was 'less then ideal' and you'll have to code review the whole thing, but it did the job 😉
That said, it's not scalable and maintainable, but a solid staring point if you want to roll your own, and ai will make sure you're conforming to any standards you want to specify, and can completely rewrite code based on another standard if you want to see the difference.quetwo
05/13/2025, 11:24 PMdawesi
05/13/2025, 11:36 PMBill Nourse
05/13/2025, 11:36 PMdawesi
05/13/2025, 11:45 PMdawesi
05/13/2025, 11:48 PM