DEV Community

DevLog 20250514: Multitenant and API Gateway Setup

Multitenant

This might not be as relevant for self-hosted services, but for anyone providing software as a service and offering dedicated URL paths or subdomains for individual users, after deciding whether to use subdomains (e.g. https://<user>.domain.com) or paths (e.g. https://domain.com/<user>), the question of how to route traffic becomes important.

Example from Itch io

Example from LinkedIn

The first scheme allows natural mapping to individual host machines, since on DNS the subdomain can be managed with an A record. Handling paths, on the other hand, can be trickier. For instance, in CloudFlare, you cannot just tunnel things to a subservice — the path here just provides some sort of submapping/filtering and must be handled by the server (i.e. the server program must explicitly handle /path).

Cloudflare tunnel path setting screenshot

It's said that when using ASP.NET Core, one can make use of YARP for easier reverse proxy implementation instead of rolling a custom one.

Lambda

I've had fairly smooth experience with AWS Lambda before for scheduling a Python script every 15 days — providing custom libraries is a bit tricky with .zip upload, but otherwise the service runs happily and freely for the purpose.

Here at Methodox, we absolutely prefer everything in native C#, as the entire Divooka ecosystem is C#-native. As it turns out, shipping Lambda functions with the .NET runtime is even easier than dealing with Python — since for any "platform-dependent" C# (.NET 8) distribution, all dependencies are just regular DLL files, and uploading it as a plain .zip is as easy as it gets.

The AWS C# templates provide a straightforward starting point but are mostly for configuring assembly-level JSON serialization rules.

using Amazon.Lambda.Core;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
public class Function
{
    public async Task<string> FunctionHandler(ILambdaContext context)
    {
        return """
            <!DOCTYPE html>
            <html lang="en">
            <head>
              <meta charset="utf-8">
              <title></title>
              <link href="style.css" rel="stylesheet" />
            </head>
            <body>
                <h1>Hello World!</h1>
            </body>
            </html>
            """;
    }
}
Enter fullscreen mode Exit fullscreen mode

Here is a function expecting a JSON payload (e.g. in POST):

using Amazon.Lambda.Core;
using OpenAI.Chat;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
public class Function
{
    private readonly ChatClient _chatClient;
    public Function()
    {
        string? apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
        if (string.IsNullOrEmpty(apiKey))
            throw new InvalidOperationException("Environment variable OPENAI_API_KEY is not set.");

        // Initialize ChatClient for the gpt-3.5-turbo model
        _chatClient = new ChatClient(model: "gpt-3.5-turbo", apiKey: apiKey);
    }
    public async Task<string> FunctionHandler(Input input, ILambdaContext context)
    {
        if (input == null || string.IsNullOrEmpty(input.Prompt))
            throw new ArgumentException("Input must contain a non-empty Prompt.", nameof(input));

        // Call OpenAI and return the first chunk of text
        System.ClientModel.ClientResult<ChatCompletion> completion = await _chatClient.CompleteChatAsync(input.Prompt);
        return completion.Value.Content[0].Text;
    }
}

/// <summary>
/// JSON input schema for the Lambda function.
/// </summary>
public class Input
{
    public string Prompt { get; set; } = string.Empty;
}
Enter fullscreen mode Exit fullscreen mode

API Gateway

AWS offers a few similar platforms for front-facing services, and at a glance it can be a bit confusing, but it all makes a lot of sense when understood properly:

  • Lambda is where the runtime is hosted and where things actually get executed.
  • API Gateway comes with handy path configuration and response header overrides, and is used to aggregate/consume different services and expose them as structured endpoints.
  • CloudFront is for CDN and supposedly provides better caching, though I'm not super clear on its features/architecture yet.

With these technologies, everything is supposed to be API-controllable, with no need for a single local server or explicit proxy handling — just do some configuration in the console.

Conclusion

I think this is as big a discovery as CloudFlare tunneling, and it immediately opens new doors. This setup should provide enough infrastructure for a barebone multitenant service.

What's more, the free-tier nature of many AWS services — including Lambda, API Gateway, DynamoDB, and CloudFront — enables some very creative combinations when it comes to service/endpoint distributions, especially during development or for distributed purposes at any scale.

Summary of new capabilities we've discovered over the past few weeks:

  1. CloudFlare for tunneling: allows local dev server deployment.
  2. AWS Lambda: serverless workloads with dynamic responses. Use for websites and APIs.
  3. API Gateway: effectively enables multitenant setup.

Top comments (0)

OSZAR »