I just read a recent article by Raymond Camden on ...
# cfml-general
b
I just read a recent article by Raymond Camden on how to create an API, which is great. He talks about a typical use case that uses URLs like this... yourdomain.com/api.cfc?method=getcats Okay, it works. But what if you want friendly URLs and the ability to respond with JSON, XML, or HTML? Something like this... yourdomain.com/getcats yourdomain.com/getcats?name=barney Well you can... by creating a route using a little rewrite magic in a CommandBox rules file... "regex( '^/getcats$' ) -> rewrite( '/getcats/controller.cfm' )" But what if you don't want to use URL parameters?... yourdomain.com/getcats/barney You can make that work too... with an additional rewrite... "regex( '^/getcats/([a-zA-Z0-9_-]+)/?$' ) -> rewrite( '/getcats/controller.cfm?name=$1' )" In both cases, the request is routed to /getcats/controller.cfm which can handle your GET, POST, PATCH, and DELETE requests. Of course, a production API is more involved, but the core does not have to be complicated. Happy Mother's Day
s
even better in Coldbox
Copy code
.route( '/invoice/:invoiceID-numeric/refund' )
        .withAction( {
            POST : 'refund'
        } )
        .toHandler( 'invoice' )
no reason to use views/.cfm files in an API
or different action based on the HTTP verb
Copy code
.route( '/eventSignup/:eventSignupID{36}' )
        .withAction({
            GET = 'getEventSignup',
            PUT = "updateEventSignup",
        } )
        .toHandler( 'events' )
b
API best-practice suggests that the Uniform Resource Identifier (URI) refer to a resource. A resource is usually a noun, representing a collection. So, you wont get the full benefit of APIs if you use commands (for example 'getcats',) or singular nouns (for example, 'invoice' or 'eventSignup') Examples of resources are 'cats', 'invoices' and 'eventsignups'. The respective URIs end in: yourDomain.com/cats yourDomain.com/invoices yourDomain.com/eventsignups
☝🏻 1
s
I am aware of, and do not agree with, the convention of using only plural nouns. Language distinguishes between singular and plural for a reason. Sometimes you are dealing with
user
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.
b
You've hit the nail on the head. I agree with you that
users
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>
👍🏻 1
s
In our case that would communicate less information about the resource to the consumers of the API. I think you are saying 'best practice' when you mean 'convention,' and like many conventions, it's there for a reason but it's also broken for a reason. The consumers of our API (who are the reason it exists) appreciate being able to tell at a glance whether an endpoint is an interaction with a thing or multiple things more than they appreciate the medal of honor we receive for obeying a convention that, in our particular case, has very little value.
b
It is worth repeating that using the plural form conveys much more information than otherwise. And, it is best-practice, not simply a convention. But you don't have to take my word for it. Do a Google search for REST API best-practices. In any case, it just may be that you and I have different ideas about REST API design. For example, an important consideration in your design is:
Copy code
being 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.
s
You are not a constituent or a consumer of the API. We design for the people who are. I do not agree that making all nouns plural conveys more information, and from a semantic, language point of view, plural means 'more than one' so whether that information is useful to you is a matter of your preference, but whether it is useful for what we are doing is not something you could possibly know. 'person has opinion on Internet' is not news though
q
If you are ever looking for a less ColdBox centric way to do restful APIs, I did a presentation on this a year back for the Mid-Michigan ColdFusion User's Group :

https://youtu.be/FUrwMKN9mmU?feature=shared

👍 1
b
Hi @sknowlton, You give me more credit than I deserve. I am not versed in semantics. I am therefore not in a position to say whether or not "making all nouns plural conveys more information". Neither am I in a position to argue about the benefits of the plural form versus the singular form in conveying information "from a semantic, language point of view". I also don't know your application. Neither am I aware of the circumstances in which you designed it. Right from the start, my intention has been to discuss REST API design principles. In general. Abiding by them is crucial for building scalable and evolvable systems. However, the constraints from those principles make REST API hard to do. In fact, I hope fellow developers will agree with the observation, "Doing REST API is easy; doing REST API well is very hard". That has been my experience anyway. The principles, and their recommended best-practices, are readily available on the web. They are directed at every API developer. You and me included. So, the opinions, feedback and recommendations I have shared here are not directed at your particular application. If you think they are, then I am sorry. That was not my intention.
d
That said any item say
/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)
b
Thanks, @dawesi . In answer to your question,
Copy code
like 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.
👍 1
b
Hey everybody, thanks for your comments. I started this thread talking about URL rewrites, not REST best practices. But so it goes. When I mentioned method=getcats, I was refering to what Raymond Camden wrote... it just struck me as funny, maybe because I love cats! (dogs too). Anyhow, folks have a point to use plural nouns describing collections, makes sense, and follows the design pattern of many (maybe most) APIs. But I have to defend sknowlton too, "best practices" don't amount to a hill of beans if it doesn't follow what your consumers want. I personally don't think the world will end if your endpoint was /user/1234 instead of /users/1234. Buy hey, I don't always like to color inside the lines either. Oh, shout out to @quetwo, thanks for sharing the video. I'm planning on spending quality time on Lucee REST methods. If I can build scalable APIs and microservices without a framework, I'm all in.
👍 1
d
I guess that's the challenge, a framework allows you to scale without rewriting boilerplate, however sometimes boilerplate executes faster without the overhead. I'm going through this challenge with rewrite of our teamcfml framework, we need it to be both microservice and monolythic so we could roll out a static rest api with almost zero framework overhead, but then tweak to add in dynamic endpoints by simply activating extensions for functionalies. I want to be able to take a csv/json file and drop it into a directory and say, hey that's endpoint /mydata - go serve it and have almost zero overhead, maybe even in a lambda or function one day. That said if you go to github copilot (aka chatgpt4) with claude 3.7 sonnet thinking (vscode integrated), you can ask it to write you a
"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.
q
@Bill Nourse -- I use it for most of the apps I support. The only thing that you don't get out of the box is rate limiting. Authentication can be done within a few lines of code (and one day, if I ever get time, I'll publish the oAuth extension that will make auth for REST and regular endpoints a lot easier too for device and browser flows).
d
I know lucee desperately needs a turnkey oAuth extension. Been pitching one using scribe-java. There are some cfml examples or rate limiting around, other option is something like using cloudflare/azure/aws to do that for you as part of waf strategy (if public endpoint)
b
Hey quetwo, that is awesome. Rate limiting could be handled by adding rules in Cloudflare. I'm looking forward to learning more. Hey Dawesi, totally cool and I can definitely relate. Yes, it makes sense to buy into a framework, even if it is your own framework. I've done that myself. But it begs the question I have lately... Should you use the same framework for a Microservice/API as you do for a monolith app? I have a need for speed as they say :) That's why I'm thinking in terms of bare metal if at all possible... As long as it performs well, and is easy to maintain.
d
@Bill Nourse you can use a framework for both that if it is designed from the ground up to support that. in the last few weeks I've done this: i asked ai what it thought of my framework and code design choices (after explaining it in english to the ai and showing the codebase) it was a great way to see how i could improve it also, based on advances in other languages/frameworks also. a great asset to have as a tool for high level questions. It said it was perfectly suited for both microservices and monolythic due to it's extension structure (snap in only what you need for that app and nothing else, api can also be session or not session based.) asked a LOT of questions of our framework in this new rebuild, so you're right, as bare metal as possible for apis, and webhook injestion, but also with security and validation of inputs (if required - not required with read only api suite), Sometimes a completely different read only endpoint a an app to the write endpoint is a better solution for performance also. I did find taffy a great api drop in for quick and dirty api also if you dont mind having one component per endpoint.
for example some of our endpoints (aka lucee instance) are a single instance with data ETL webhook triggers (or ecommerce webhooks), so we need that to be light and as fast as possible so webhooks dont fail due to timeouts etc, but also need ability to validate and authenticate webhooks (in headers) as they arrive for security, and for setting them up (like stripe's setup dance)