Building Fast Serverless APIs With Minimal APIs on AWS Lambda

Building Fast Serverless APIs With Minimal APIs on AWS Lambda

5 min read ·

dotUltimate: The complete developer toolkit to cover all your .NET and game development needs, supercharged with JetBrains AI. Enjoy access to Rider, ReSharper for Visual Studio and Visual Studio Code, dotTrace, dotMemory, dotCover, dotPeek, JetBrains AI, and more tools with a single subscription - no need for multiple licenses. Download now

Rovo Dev in GA: Meet Atlassian's context-aware AI agent for the entire SDLC. From code gen and review to docs and maintenance, Rovo Dev helps you automate, write, and accelerate. Use Rovo Dev in Jira, your IDE, your terminal, and source control (Bitbucket & GitHub) to stay in flow. Explore Rovo Dev.

Have you ever wondered if you could host a tiny .NET API without running servers 24/7? You can!

AWS Lambda lets you run code on-demand, and with the Amazon.Lambda.AspNetCoreServer.Hosting library, you can plug an ASP.NET Core Minimal APIs straight into AWS Lambda.

In this article we'll set up a minimal API, deploy it, and discuss how it performs.

Don't worry if you're new to serverless, I'll keep everything simple enough to follow along.

Getting Your Minimal APIs Lambda-Ready

Let's start with the basics. You need just three things to turn your Minimal API into a Lambda function.

First, create your API:

dotnet new webapi -n MyLambdaApi
cd MyLambdaApi

Second, add Amazon's hosting package:

Install-Package Amazon.Lambda.AspNetCoreServer.Hosting

Third, add one line to your Program.cs:

var builder = WebApplication.CreateBuilder(args);

// This line does all the Lambda magic
builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi);

var app = builder.Build();

app.MapGet("/", () => "Hello from Lambda!");

app.Run();

That's it. Your API now runs both locally (for testing) and in Lambda (for production). When you run locally, it uses Kestrel like normal. When you deploy the app to AWS, Lambda takes over.

Ship It to AWS

You'll need the Lambda tools installed:

dotnet tool install -g Amazon.Lambda.Tools

Then deploy with one command:

dotnet lambda deploy-function

The tool asks you a few questions (like the function name, which IAM role to use). Pick the defaults if you're just testing. In a minute or two, your API is live with a URL like: https://[abc123xyz].lambda-url.[region-name].on.aws/.

From the AWS Management Console, you can find your function with its URL and all the other details.

Measuring Cold Starts

Here's where things become interesting (and problematic). Lambda functions "go to sleep" when nobody uses them. Waking them up takes time, this is the famous "cold start" problem.

I ran some simple tests with a basic Minimal API:

  • First request (cold): 2,153 ms
  • Second request (warm): 154 ms
  • Third request (warm): 143 ms
  • After 10 minutes idle (cold again): 2,074 ms

As you can see, the first request is slow due to the cold start. Subsequent requests are much faster, around 150 ms. After 10 minutes of inactivity, the function goes cold again, and the next request takes over 2 seconds. There's an optimization feature called SnapStart that can help reduce cold start times, but it has its own tradeoffs and isn't always suitable for every application.

Another thing to note is that I'm in Europe, and my Lambda function is in the US East (N. Virginia) region. This adds network latency, so your results may vary based on your location and the region you choose.

Even when warm, the latency is higher than a typical server-hosted API. This may be acceptable for low-traffic or non-critical endpoints, but it's something to consider.

Let's move beyond a "Hello World" example to something more realistic.

CRUD Operations Benchmark

To see how a simple API performs in the real world, I built a tiny CRUD app that creates a record, fetches it, updates it and then deletes it. First I ran each operation once to see the raw latency. Then I used a load-testing tool with 100 virtual users (VUs) to measure average latency under a bit more stress.

// POST /products - Create new product
app.MapPost("/products", async (CreateProductRequest request, NpgsqlDataSource dataSource) =>
{
    const string sql =
        """
        INSERT INTO Products (Name, Description, Price, CreatedAt)
        VALUES (@Name, @Description, @Price, @CreatedAt)
        RETURNING Id, Name, Description, Price, CreatedAt
        """;

    await using var connection = await dataSource.OpenConnectionAsync();
    var product = await connection.QueryFirstAsync<Product>(sql, new
    {
        request.Name,
        request.Description,
        request.Price,
        CreatedAt = DateTime.UtcNow
    });

    return Results.Created(
        $"/products/{product.Id}",
        new ProductResponse(
            product.Id,
            product.Name,
            product.Description,
            product.Price,
            product.CreatedAt));
});
// Other endpoints omitted for brevity:
// - GET /products/{id} - Get product by ID
// - PUT /products/{id} - Update product
// - DELETE /products/{id} - Delete product

The application uses .NET 8, Npgsql and Dapper to interact with a PostgreSQL database running on Amazon RDS. You can find the source code for this example (and the previous one) in this repository.

Here are the results:

Single-call latency

| Operation  | Latency (ms) | Notes                           |
| ---------- | ------------ | ------------------------------- |
| Create     |     537      | Cold start plus object creation |
| Read       |     134      | Simple GET of the new record    |
| Update     |     140      | Changing one property           |
| Delete     |     167      | Removing the record             |

The create call took half a second because it included a cold start and initialization overhead. Once the function was warmed up, the other operations completed in under two hundred milliseconds.

Load test with 100 virtual users

During the load test, I simulated 100 clients hitting the API at once. AWS automatically scaled the Lambda function to handle the traffic, and average latencies dropped because the functions were already warm. Here are the averages:

  • CREATE avg: 129 ms
  • READ avg: 132 ms
  • UPDATE avg: 152 ms
  • DELETE avg: 144 ms

These numbers show that once your function is up and running, Lambda can respond quite quickly even when many users are making requests. Of course, actual times will vary depending on what your API does and how it stores data.

Remember that each of these operations involves network calls to the database, which adds latency. Overall, I don't find these numbers bad for a serverless setup.

Summary

Hosting minimal APIs in AWS Lambda is surprisingly straightforward. With one library and a single method call, your ASP.NET Core code can run "without servers". You pay only for the compute you use. Also, the AWS Lambda free tier includes one million free requests per month, which is great for testing and light usage.

However, there are tradeoffs. Because of cold starts and the overhead of starting the .NET runtime, latency isn't as low as hosting on a dedicated server. If your API is mission-critical or requires sub-100 ms responses at all times, you may need to look at provisioned concurrency, containers, or a traditional host.

Lambda shines for small, event-driven or intermittent workloads that don't justify a full-time server. But it's less suitable for latency-sensitive or heavy, long-running applications. For occasional or low-traffic API endpoints, though, Lambda offers a cost-effective and simple option.

That's all for today.

See you next week.


Whenever you're ready, there are 4 ways I can help you:

  1. Pragmatic Clean Architecture: Join 4,200+ students in this comprehensive course that will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture.
  2. Modular Monolith Architecture: Join 2,100+ engineers in this in-depth course that will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario.
  3. (NEW) Pragmatic REST APIs: Join 1,200+ students in this course that will teach you how to build production-ready REST APIs using the latest ASP.NET Core features and best practices. It includes a fully functional UI application that we'll integrate with the REST API.
  4. Patreon Community: Join a community of 5,000+ engineers and software architects. You will also unlock access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses.

Become a Better .NET Software Engineer

Join 72,000+ engineers who are improving their skills every Saturday morning.