Multiple redirects in netlify.toml? Subdirectory functions not being served

I am making a publicly accessible API. I am trying to get ahead of the need to update it in the future by introducing versioning. By default, netlify.toml contains these lines:

[[redirects]]
  from = "/api/*" # simplify all calls to serverless functions
  to = "/.netlify/functions/:splat" # all function calls will go to this path

At first, I started putting my function files in subdirectories of /.netlify/functions/ So for example, I made these two:

/.netlify/functions/v1/hello.js
/.netlify/functions/v2/hello.js

(corrected)

/netlify/functions/v1/hello.js
/netlify/functions/v2/hello.js

I would then try to visit them (hosted locally with the CLI app) by going to:

http://localhost:8888/api/v1/hello
http://localhost:8888/api/v2/hello

My browser displayed both of these as “Function not found…” Whereas the CLI printed:

                                                                               ◈ Rewrote URL to /.netlify/functions/v1/hello
Request from ::1: GET /.netlify/functions/v1/hello
Response with status 404 in 0 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello.html
Request from ::1: GET /.netlify/functions/v1/hello.html
Response with status 404 in 0 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello.htm
Request from ::1: GET /.netlify/functions/v1/hello.htm
Response with status 404 in 0 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello/index.html
Request from ::1: GET /.netlify/functions/v1/hello/index.html
Response with status 404 in 0 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello/index.htm
Request from ::1: GET /.netlify/functions/v1/hello/index.htm
Response with status 404 in 1 ms.

On the other hand, any .js or .ts files that are directly in /.netlify/functions (and not a subdirectory) still run fine.

Next thing I tried was to rewrite the redirects section of the TOML file:

[[redirects]]
  from = "/api/v1/*" 
  to = "/.netlify/functions/v1:splat"
  from = "/api/v2/*" 
  to = "/.netlify/functions/v2:splat"

But it seems that the server doesn’t like multiple “from” and “to” entries; I suppose these are meant to be unique identifiers. The CLI said:

When resolving config file <C:\MyProjectPath>\netlify.toml:
Could not parse configuration file
Cannot redefine existing key 'redirects.from'.

Any solution to versioning my API here?

This is where the functions are built to, not where you put the source. Source goes in netlify/functions/[filename].{ts|js}

You would need to make these two separate rules i.e.

[[redirects]]
  from = "/api/v1/*" 
  to = "/.netlify/functions/v1:splat"
  status = 200

[[redirects]]
  from = "/api/v2/*" 
  to = "/.netlify/functions/v2:splat"
  status = 200

Do also note what is stated in the Create functions → Name your function documentation

ou can store your function file directly under the functions directory or in a subdirectory dedicated to the function.

So you can have

netlify
└── functions
    ├── test
    │   └── test.js
    ├── v1
    │   └── v1.js
    └── v2
        └── v2.js

but you cannot have

netlify
└── functions
    ├── test
    │   ├── alpha.js
    │   └── beta.js
    ├── v1
    │   ├── goodbye.js
    │   └── hello.js
    └── v2
        ├── goodbye.js
        └── hello.js

Whoops, that’s my mistake! Corrected in the original post. I actually did put them in the /netlify directory, not /.netlify.

However the result was the same. I tried this:

[[redirects]]
  from = "/api/v1/*" # simplify all calls to serverless functions
  to = "/.netlify/functions/v1/:splat" # all function calls will go to this path
  status = 200 # ok code
  force = true # ensure to always redirect

[[redirects]]
  from = "/api/v2/*" # simplify all calls to serverless functions
  to = "/.netlify/functions/v2/:splat" # all function calls will go to this path
  status = 200 # ok code
  force = true # ensure to always redirect

So then I go to http://localhost:8888/api/v1/hello - the browser says “Function not found…” and the CLI outputs

◈ Rewrote URL to /.netlify/functions/v1/hello
Request from ::1: GET /.netlify/functions/v1/hello
Response with status 404 in 2 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello.html
Request from ::1: GET /.netlify/functions/v1/hello.html
Response with status 404 in 0 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello.htm
Request from ::1: GET /.netlify/functions/v1/hello.htm
Response with status 404 in 0 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello/index.html
Request from ::1: GET /.netlify/functions/v1/hello/index.html
Response with status 404 in 0 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello/index.htm
Request from ::1: GET /.netlify/functions/v1/hello/index.htm
Response with status 404 in 0 ms.

So the redirect is working. BUT it seems that anything that is in a subdirectory of the functions directory is not recognized as a function.

I then saw in the docs that you can specify a functions directory in netlify.toml, so I tried this:

[functions]
  node_bundler = "esbuild"
  directory = "/.netlify/functions/v1/:splat"

But this only handles one of the subdirectories - “directory” has to be unique, so adding another line causes the config to be invalid again.

Just saw your second update, will try that.

Thanks for looking at this with me. I see that I was not following the documented rule of giving each function in a subdirectory its own subdirectory, either with the same name, or with the file renamed to index.

So I tried moving /netlify/functions/v1/hello.ts to the following locations:

/netlify/functions/v1/hello/hello.ts
/netlify/functions/v1/hello/index.ts

And actually, neither worked. Once again, the redirects seemed to work but it did not treat them as functions:

◈ Rewrote URL to /.netlify/functions/v1/hello
Request from ::1: GET /.netlify/functions/v1/hello
Response with status 404 in 0 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello.html
Request from ::1: GET /.netlify/functions/v1/hello.html
Response with status 404 in 0 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello.htm
Request from ::1: GET /.netlify/functions/v1/hello.htm
Response with status 404 in 1 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello/index.html
Request from ::1: GET /.netlify/functions/v1/hello/index.html
Response with status 404 in 1 ms.
◈ Rewrote URL to /.netlify/functions/v1/hello/index.htm
Request from ::1: GET /.netlify/functions/v1/hello/index.htm
Response with status 404 in 0 ms.

This is a surprise because it does seem to follow the documentation now. Maybe I’ve screwed up some config elsewhere?

Update:
Actually, if I understand the CLI output, it seems that the default redirect rule should be fine in this case, because the problem isn’t the redirection of the request. The problem is that it isn’t serving the function.

With the local server running, I am looking at the contents of the /.netlify/functions-serve directory, and it has a directory for each of my functions that are directly inside the /netlify/functions source directory. However, there is no v1 or v2 directory. So something seems messed up at this step.

Update 2:
I’ve tried renaming /netlify/functions/v2/hello.ts to /netlify/functions/v2/v2.ts and… it worked!

However, this is concerning. Does this mean you only get to use a depth of one subdirectory? This will be a problem for doing a versioned API… Unless there is another workaround.

The reason, as eluded to in the documentation, though perhaps not explicitly stated, is that having functions multiple-level deep doesn’t work.

1 Like

Just arrived at that conclusion too… so it’s looking like a versioned API with directories is not possible to support :pensive:

Netlify is designed to serve statically built sites to humans. Yes, there are serverless and edge functions. But a full-scale API, this isn’t (IMHO) something Netlify is built for.

I use another well-known platform which has workers that sit on the edge for anything API related. Site front-end on Netlify, back-end elsewhere. That’s my preference (FWIW.)

Happy to take recommendations if it doesn’t get the thread obliterated…

Without naming names, I already gave a hint in my previous post :wink:

1 Like