REST Endpoints in directories under the root mapping

So here’s the problem. Lucee is not reading into subdirectories below the api
directory mapping. (This is what the documentation is saying in part 5)

I have three CFC’s.

/api/test-a.cfc
/api/test-b.cfc
/api/v1/test-c.cfc

Each cfc has one component with a rest endpoint defined.

/api/test-a.cfc has “resta” defined at the component level. You can hit it with a browser and get a successful return.

/api/test-b.cfc has “/v1/restb/abc” defined at both the component level (v1/restb) and the function level (abc). Likewise, you can hit it with a browser and get a successful return.

These two work fine.

However, the problem is that ANY cfc’s in a subdirectory under “/api” do not work.

For example, I have a subdirectory called “/api/v1” and inside it I have a cfc called “test-c.cfc”. In that cfc is a component with a component level rest path of “/v1/testc” defined. Normally, if you hit
the endpoint with that path, it should work but it doesn’t.

This is the problem that I think the documentation (Section 5) is referring to.
(See REST Services: Introduction :: Lucee Documentation)
Something on the server needs to be set to let Lucee understand pathing for REST services in directories that exist below the root mapping. In my case “/api”.

Why is this important? When organizing REST services, you have to be able to organize the CFCs in logical directories. Otherwise, it’s a mess to manage.
ColdFusion allows for this type of organization, and I believe Lucee may as well. Except Lucee needs some extra TLC to get it to work. Thus, the documentation states that you have to configure something on the webserver in such a way that Lucee can recognize other endpoints in subdirectories. At least that’s how I’m reading the documentation.

I’m new to Lucee so I’m deferring to someone in support who can help me close this last gap.

OS: Linux
Java Version: 11.0.21
Tomcat Version: Apache Tomcat/9.0.31
Lucee Version: 5.3.11.5

I’ve written a great part of that documentation and tested a lot, but have never written bigger rest applications with RESt

All what is written in Part 5 is that if you want to access your REST endpoint going through your webserver at port 80/443, you MUST make sure at the webserver, that those requeests are intercepted and passed to Tomcat/Lucee.

  • Where/how have you defined the rest mappings? admin/Application.cfc or somewhere else?

As far as I remember, you need to structure your components not by the structure of the physical file paths, but purely by what was initilazed in restinitapplication() with servicemapping and submitted and intercept reequest on top by restpath attributes. Shouldn’t you map a rest application servicemappingcto to “api/v1/” and then used the components inside using the restpath attibute to map them to those?

Typically, when a rest mapping is created in the LUCEE admin, that mapping tells the server to look for all REST enabled components in the registered directory. In my case it looks something like:

Physical Path: /home/domain/api
Virtual Path: /api

When I place any REST enabled component in there, Lucee processes the component requests as an endpoint.

If I create a subdirectory under /api like /api/v1 the expectation is that Lucee would read down the directory tree and register any rest enabled components in them as well. Lucee should ignore the actual physical directory structure where all the rest enabled components are. Technically it’s irrelevant because the RESTPATH definitions at the component level or method level actually determine pathing for endpoints, not the directory structure they find themselves in. The actual physical directory structure is only for the developer’s benefit in organizing rest endpoints.

What I need to know is, does Lucee ignore the components in subdirectories under the main rest mapped directory? OR is Lucee supposed to be looking down the directory tree but for some reason is not.

One last example:

Components:
/api/canary.cfc (endpoint /api/canary) ← This call works
/api/v1/canary.cfc (endpoint /api/v1/canary) ← This call does NOT work. Why?

Just for my understanding…
You have:

  • created one and only servicemapping named “api” in the admin.
  • Placed a component physically in /api/v1/canary.cfc

Is that correct?

Question:
What would you expect to happen if you add a component named
v1.cfc with a RESTPATH “canary”? Ok… that doesn’t make much sense, but just the option that this would then just be possible disregarding the name. That would create totally unexpected results because each subdirectory would “compete” with any named cfc whenever both have equal names, right? What would be your expectation for
/api/canary/some.cfc
/api/canary.cfc (with the method “some”)

I’m not a REST pro at all, I have used REST for small use cases only. But from the fact above and from my personal understanding about what I have read and tried, my personal expectation would be to add a servicemapping as “api/v1” and another for “api/v2/” and keep the components just in place without any subdirectories. If using any subdirectory I would cfdirectory those on initialization and add a rest service mapping programmatically to keep full control about any rest url.

Maybe some more experienced REST devs could chime in and help out. Maybe using taffy or the ortus rest solution would be an alternative for you?

Ya I’m not sure you’re understanding how REST paths work.

Rest paths defined in components are completely independent of their file names by design. I’ll explain.

Here is a typical REST service design for a sample application that manages bicycles.

We will assume /api/ is the Virtual Path and /home/domain.com/api/ is the physical path on the server.

REST paths for the application would be organized like this:

//USER ENDPOINTS

COMPONENT LEVEL RESTPATH=“/api/v1/users”

function=getAllUsers METHOD=GET
function=getUserByID METHOD=GET RESTPATH=“/[userID]”
function=addUser METHOD=POST
function=updateUser METHOD=PUT
function=deleteUser METHOD=DELETE RESTPATH=“/[userID]”

The user endpoints all exist in ONE component (cfc file). Each function inherits it’s restpath from the component restpath.

They all share the same physical file on the server:
/home/domain.com/api/v1/users.cfc

Now this is where it gets a little more complicated:

//CYCLING ENDPOINTS

//Managing Bicycles

//The parent restpath for cycling is /api/v1/cycling. I create a physical
directory as follows: /home/domain.com/api/v1/cycling

//All endpoints related to cycling will use this base restpath.

//Under this path are specific endpoints defined for specific tasks related to
cycling such as managing bikes and bike categories. Notice how I structure the
restpaths relative to their physical directories. I mirror them for
organizational purposes.

//Managing bikes

COMPONENT LEVEL RESTPATH=“/api/v1/cycling/bikes”
PHYSICAL PATH ON SERVER IS /home/domain.com/api/v1/cycling/bikes-CR.cfc

//Get and add functions go here
function=getAllBikes METHOD=GET
function=getBikeByID METHOD=GET RESTPATH=“/[bikeID]”
function=addBike METHOD=POST

//… at this point, the code inside the CFC may be getting a bit long so I
create a new CFC to continue my work managing bike data.

COMPONENT LEVEL RESTPATH=“/api/v1/cycling/bikes” ← IMPORTANT!!! Notice I’m using the SAME component restpath signature. This is allowed as long as function restpaths and methods do not create a duplicate endpoint as follows.
PHYSICAL PATH ON SERVER IS /home/domain.com/api/v1/cycling/bikes-UD.cfc

//Edit and delete functions go here
function=updateBike METHOD=PUT
function=deleteBike METHOD=DELETE RESTPATH=“/[bikeID]”

//The server doesn’t care that I’ve done this so long as it doesn’t find duplicate restpath signatures. The reason being that the developer needs to have the flexibility to organize his code properly.

//Managing bike categories

COMPONENT LEVEL RESTPATH=“/api/v1/cycling/categories”
PHYSICAL PATH ON SERVER IS /home/domain.com/api/v1/cycling/categories.cfc

function=getAllBikeCategories METHOD=GET
function=getBikeCategoryByID METHOD=GET RESTPATH=“/[bikeID]”
function=addBikeCategory METHOD=POST
function=updateBikeCategory METHOD=PUT
function=deleteBikeCategory METHOD=DELETE RESTPATH=“/[bikeID]”

Notice how I organize the application? I mirror the restpaths as close to the physical directories as I can to ensure I don’t have enormous monolythic components. I break out the logical functions into their own components for maintainability.

In the ColdFusion world from where I’m coming from (and .NET in my daytime job) I am able to do this
without issues. Because CF and Lucee derive from the same JAVA origins, I cannot imagine they would behave differently.

The big question is figuring out why Lucee isn’t recognizing legitimate restpaths in subdirectories off the mapped root directory.

Maybe it’s a bug in Lucee?