Skip to content
Shiny.Net.HttpServer v1 - A lightweight feature rich HTTP Server - Tunnels, Websockets, AOT, ASPNET Featureset, & Works EVERYWHERE!Let me see!

Getting Started

ASP.NET Core is heavyweight and does not run on .NET MAUI or in several embedded server scenarios. This is a dependency-light, AOT- and trim-clean HTTP/1.1, HTTP/2 and HTTP/3 server that runs anywhere .NET runs — plus tunnelling, so a server embedded in a phone app is reachable from the public internet.

Only Microsoft.Extensions.* abstractions are taken as dependencies. Everything else — JSON, crypto, JWT, OpenAPI, HPACK, QPACK — is built on what is in the box.

GitHub GitHub stars for shinyorg/httpserver
Downloads NuGet downloads for Shiny.Net.HttpServer
Frameworks
.NET
.NET MAUI
Operating Systems
Android
iOS
macOS
Windows
Linux
Package Description
NuGet package Shiny.Net.HttpServer The server: HTTP/1.1, HTTP/2 & HTTP/3, routing, middleware, DI scopes, static files, WebSockets, SSE, sessions, OpenAPI, CORS, rate limiting, tunnelling
NuGet package Shiny.Net.HttpServer.SourceGenerators Compile-time typed endpoints — route registration, parameter binding, OpenAPI. Zero reflection
NuGet package Shiny.Net.HttpServer.Jwt JWT authentication on in-box crypto — no Microsoft.IdentityModel dependency
NuGet package Shiny.Net.HttpServer.AzureRelay Azure Relay tunnel provider
NuGet package Shiny.Net.HttpServer.Ssh SSH remote-forwarding tunnel provider, including zero-account quick tunnels
NuGet package Shiny.Net.HttpServer.Mcp Model Context Protocol transport — host an MCP server without ASP.NET Core

Everything shipping targets net10.0 and has the trim, AOT and single-file analyzers turned on, so “AOT-clean” is enforced by the build rather than claimed in a readme.

Terminal window
dotnet add package Shiny.Net.HttpServer
# optional, and what tier 3 below needs
dotnet add package Shiny.Net.HttpServer.SourceGenerators

The generator package is an analyzer — it runs inside the compiler and never lands in your output.

using Shiny.Net.HttpServer;
var server = new HttpServer(new HttpServerOptions { Port = 8080 });
server.OnGet("/ping", ctx => ctx.Response.WriteAsync("pong"));
await server.RunAsync();

That is the whole ceremony. No container, no host builder, no configuration file. The server binds loopback by default — a server embedded in a mobile app should not be reachable from the local network unless its author says so.

The point of the API is a gentle ramp: trivial to start, strongly typed when you want it. Each tier is built on the one below and they compose in the same app.

  1. Tier 0 — one delegate, no routing.

    server.OnRequest(ctx => ctx.Response.WriteAsync("hello"));
  2. Tier 1 — raw routing. OnGet/OnPost/… (or MapGet/MapPost/…, the ASP.NET spelling).

    server.OnGet("/ping", ctx => ctx.Response.WriteAsync("pong"));
    server.OnGet("/users/{id:int}", ctx => ctx.Response.WriteAsync(ctx.Request.RouteValues["id"]!));

    See Routing.

  3. Tier 2 — middleware. The same shape as ASP.NET Core middleware, as a lambda or as a class.

    server.Use(async (ctx, next) =>
    {
    var sw = Stopwatch.StartNew();
    await next(ctx);
    logger.LogInformation("{Path} took {Elapsed}ms", ctx.Request.Path, sw.ElapsedMilliseconds);
    });

    See Middleware.

  4. Tier 3 — source-generated typed endpoints. Route registration, parameter binding and OpenAPI metadata are emitted at compile time — no reflection, which is what keeps the whole thing trim- and AOT-clean.

    [Route("/api/users")]
    public class UserEndpoints(IUserService users, ILogger<UserEndpoints> logger)
    {
    [Get("/{id:int}")]
    public async Task<IActionResult> GetUser(int id, CancellationToken ct)
    => await users.FindAsync(id, ct) is { } u ? new OkObjectResult(u) : new NotFoundResult();
    }
    app.MapMyAppEndpoints(); // emitted for every [Route] class in the assembly

    See Typed Endpoints.

Dependency injection is available, never mandatory

Section titled “Dependency injection is available, never mandatory”
// No container. RequestServices resolves nothing; everything else works.
var server = new HttpServer(new HttpServerOptions { Port = 8080 });
// With a container. Scoped services behave exactly as in ASP.NET Core.
var builder = HttpServer.CreateBuilder();
builder.Services.AddSingleton<IClock, SystemClock>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
var app = builder.Build();

A real IServiceScope is created per request/response exchange and disposed when the request ends, including IAsyncDisposable. Endpoint classes are resolved from that scope, so a Scoped dependency is one instance shared by everything handling the request — the same contract as ASP.NET Core. ctx.RequestServices is the accessor at every tier.

If the app already has a container — a MAUI app, a generic host — use AddHttpServer() instead. See Hosting & Lifecycle.

If you want to… Read
Start, stop and restart the server at runtime Hosting & Lifecycle
Bind several ports, set limits, turn on forwarded headers Configuration
Understand templates, constraints and match precedence Routing
Return JSON without reflection Results & JSON
Serve a web app out of the assembly Static Files or Blazor WebAssembly
Authenticate callers Authentication, JWT
Reach the device from the internet Tunnelling
Run this inside a .NET MAUI app .NET MAUI
Host an MCP server Model Context Protocol

This is the constraint the whole design answers to, so it is worth stating plainly: nothing in the server discovers anything by reflection. Routes are registered by generated code, parameters are bound by generated code, and JSON goes through JsonTypeInfo from a JsonSerializerContext rather than through Type.GetProperties().

The one thing you have to bring is that context:

[JsonSerializable(typeof(Widget))]
[JsonSerializable(typeof(IReadOnlyList<Widget>))]
public partial class AppJson : JsonSerializerContext;

The endpoint generator emits a module initializer that registers it for you, and warns at build time (SWS006) about any type crossing an endpoint boundary that the context does not cover — turning a runtime failure into a build warning. See Results & JSON.

The reflection-based JSON overloads still exist for apps that are not published trimmed, but they are annotated [RequiresUnreferencedCode]/[RequiresDynamicCode], so an AOT build has to opt into them deliberately.