Expose multiple api uri on the same nginx server block

What do you mean by tweaking fastcgi_param REQUEST_URI? If you try to set some custom value to REQUEST_URI before you include the fastcgi_params file, value set by fastcgi_params would overwrite any of your tweakings:

fastcgi_pass service:9000;
fastcgi_param REQUEST_URI /some/path;
include fastcgi_params;
# REQUEST_URI passed as the real request URI

However this one would work as expected:

fastcgi_pass service:9000;
include fastcgi_params;
fastcgi_param REQUEST_URI /some/path;
# REQUEST_URI passed as "/some/path"

Trying to change this with rewrite won’t work because the REQUEST_URI fastcgi parameter is set to $request_uri internal nginx variable value inside the fastcgi_params file, and that variable doesn’t changed by rewrite directive rules, it is an $uri one that does.

Here is the most simple solution that should work:

server {
    ...
    location ~ ^/api(/(?:account|cart|order|product)/.*) {
        # strip "/api" part from the URI and search for the new location block
        rewrite ^ $1 last;
    }

    location /account {
        # strip "/account" part from the URI and continue processing within the current location block
        rewrite ^/account(.*) $1 break;
        # include default fastcgi parameters first
        include fastcgi_params;
        # all our tweakings goes after it
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        # use the rewrited $uri variable instead of the default $request_uri
        # $uri variable does not include query arguments, so add them manually if they exists
        fastcgi_param REQUEST_URI $uri$is_args$args;
        fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
        fastcgi_intercept_errors on;
        fastcgi_pass account-service:9000;
    }
    location /cart {
        rewrite ^/cart(.*) $1 break;
        ...
        fastcgi_pass cart-service:9000;
    }
    location /order {
        rewrite ^/order(.*) $1 break;
        ...
        fastcgi_pass order-service:9000;
    }
    location /product {
        rewrite ^/product(.*) $1 break;
        ...
        fastcgi_pass product-service:9000;
    }
}

This solution could be greatly optimized using advanced nginx techniques:

server {
    ...
    # This is a very important one!
    # Since we are using variables for backend name, we need a resolver to resolve it at the runtime
    # Docker default internal resolver is 127.0.0.11
    resolver 127.0.0.11;

    location ~ ^/api/(?<api>account|cart|order|product)(?<path>/.*) {
        include fastcgi_params;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        # note we are using the $path variable here instead of the $uri one
        fastcgi_param REQUEST_URI $path$is_args$args;
        # assuming this path is the same within all the backend services
        fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
        fastcgi_intercept_errors on;
        # using $api variable as part of backend container name
        fastcgi_pass $api-service:9000;
    }
}

Note that we need a new resolver directive since we are using a variable to specify the backend name. You can read additional details here, and the resolver address for docker taken from this answer.


Update @ 2022.05.18

Actually there is a way to made the above config works without an additional resolver directive. To do it we need to define every service as an upstream. This way nginx will need to resolve the used service names to docker containers IP addresses only once at startup, eliminating all the internal DNS traffic and making the whole config somewhat more performant:

upstream account {
    server  account-service:9000;
}
upstream cart {
    server  cart-service:9000;
}
upstream order {
    server  order-service:9000;
}
upstream product {
    server  product-service:9000;
}

After doing that the last line from the config given above can be changed to

fastcgi_pass $api;

and the resolver directive can be safely removed from the nginx configuration.


If your script path vary upon the different API backend containers, you can use an additional map block to get the script path from the $api variable value:

map $api $script {
    account    /usr/src/app/public/index.php;
    cart       /some/other/path;
    ...
}

server {
    ...
    location ~ ^/api/(?<api>account|cart|order|product)(?<path>/.*) {
        ...
        fastcgi_param SCRIPT_FILENAME $script;
        ...
    }
}

Leave a Comment