RestLib.InMemory 2.5.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package RestLib.InMemory --version 2.5.0
                    
NuGet\Install-Package RestLib.InMemory -Version 2.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RestLib.InMemory" Version="2.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RestLib.InMemory" Version="2.5.0" />
                    
Directory.Packages.props
<PackageReference Include="RestLib.InMemory" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RestLib.InMemory --version 2.5.0
                    
#r "nuget: RestLib.InMemory, 2.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RestLib.InMemory@2.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RestLib.InMemory&version=2.5.0
                    
Install as a Cake Addin
#tool nuget:?package=RestLib.InMemory&version=2.5.0
                    
Install as a Cake Tool

RestLib

3 lines to a consistent CRUD REST API

Build Coverage NuGet License

RestLib is a .NET 10 library for ASP.NET Core Minimal APIs that generates CRUD endpoints from your model and repository. It bakes in secure defaults, cursor-based pagination APIs, filtering, sorting, field selection, HATEOAS hypermedia links, OpenAPI metadata, and RFC 9457 Problem Details so you can ship consistent APIs faster. Some capabilities depend on the repository adapter and may have implementation-specific limits.

Table of Contents

Install

Install the core package:

dotnet add package RestLib

For demos, tests, and quick prototypes, add the optional in-memory adapter:

dotnet add package RestLib.InMemory

For production use with Entity Framework Core:

dotnet add package RestLib.EntityFrameworkCore

Quick Start

Create a new app:

dotnet new web -n MyApi
cd MyApi

Define a model:

public class Product
{
    public Guid Id { get; set; }
    public required string Name { get; set; }
    public decimal Price { get; set; }
}

Configure RestLib:

using RestLib;
using RestLib.InMemory;
using Scalar.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRestLib();
builder.Services.AddRestLibInMemory<Product, Guid>(p => p.Id, Guid.NewGuid);
builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();
app.MapScalarApiReference();

app.MapRestLib<Product, Guid>("/api/products", config =>
{
    config.AllowAnonymous();
});

app.Run();

Run the app and open the API reference at /scalar:

dotnet run

That gives you:

  • GET /api/products - list all with cursor pagination
  • GET /api/products/{id} - fetch a single resource
  • POST /api/products - create
  • PUT /api/products/{id} - replace
  • PATCH /api/products/{id} - partially update
  • DELETE /api/products/{id} - delete

Quick Start (folder convention)

For JSON-driven resources, the recommended path is one file per resource under Models/.

Program.cs:

using RestLib;
using RestLib.InMemory;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRestLib();
builder.Services.AddRestLibInMemory<Product, Guid>(p => p.Id, Guid.NewGuid);
builder.Services.AddRestLibFromFolder("Models");

var app = builder.Build();
app.MapJsonResources();
app.Run();

Single-file variant:

builder.Services.AddJsonResourceFromFile<Product, Guid>("Models/Products.json");

Two-model single-file variant:

builder.Services.AddJsonResourceFromFile<ProductDto, ProductEntity, Guid>("Models/Products.json");

Models/Products.json:

{
  "$schema": "https://raw.githubusercontent.com/Adrian01987/RestLib/main/schemas/restlib-resource.schema.json",
  "EntityType": "Product, MyApi",
  "Name": "products",
  "Route": "/api/products",
  "AllowAnonymousAll": true,
  "Validation": {
    "Name": { "Required": true },
    "Price": { "Min": 0.01 }
  }
}

See docs/guides/json-resources.md for the full walkthrough.

Separate API and DB models

When you want to expose a DTO but persist a different model, register a mapper and use the three-type overload:

builder.Services.AddRestLibMapper<ProductDto, ProductEntity, ProductMapper>();

app.MapRestLib<ProductDto, ProductEntity, Guid>("/api/products", config =>
{
    config.AllowAnonymous();
    config.AllowFiltering(p => p.CategoryId);
    config.AllowSorting(p => p.Name, p => p.Price);
    config.AllowFieldSelection(p => p.Id, p => p.Name, p => p.Price);
    config.AllowSearch(p => p.Name, p => p.Description);
});

JSON resources can declare the same pattern with a Mapping section:

{
  "EntityType": "ProductDto, MyApi",
  "Name": "products",
  "Route": "/api/products",
  "Mapping": {
    "DbType": "ProductEntity, MyApi",
    "Mapper": "ProductMapper"
  },
  "Filtering": ["CategoryId"],
  "Sorting": ["Name", "Price"],
  "FieldSelection": ["Id", "Name", "Price"],
  "Search": ["Name", "Description"]
}

RestLib can opt resources into a simple OR-of-contains search across configured string properties. The default query parameter is q, and JSON resources support the same feature with configurable search options.

Search is intentionally limited to lightweight collection queries rather than full-text indexing, ranking, or fuzzy matching. For full fluent and JSON examples, nested-path usage, and the strict Mapping.Auto shortcut, see docs/guides/query-features.md.

Composite keys

RestLib supports ordered two-part composite keys through RestLibCompositeKey<TFirst, TSecond> for both fluent and JSON-backed resources. Item routes use two path segments such as /api/tenant-products/{tenantId}/{sku}.

For complete fluent setup, JSON Key examples, and related query behavior, see docs/guides/query-features.md.

EF Core Quick Start

For a database-backed path, register your DbContext, call AddRestLibEfCore<AppDbContext, TEntity, TKey>(), and map the same RestLib endpoints over your EF Core model. RestLib handles endpoint wiring and repository behavior, while your application continues to own schema creation and migrations.

The sample app uses EnsureCreated() as a zero-setup demo path, but production EF Core apps should use normal migrations and Database.Migrate(). For the full setup and migration workflow, see docs/guides/ef-core-migrations.md.

Why RestLib

Every backend project starts the same way: define a model, write CRUD endpoints, add validation, handle errors, set up pagination, wire Swagger, and repeat for every entity.

RestLib removes that repetition while keeping the parts that matter explicit:

  • Proper REST semantics inspired by the Zalando REST API Guidelines
  • Secure-by-default endpoints with per-operation opt-out
  • Machine-readable RFC 9457 Problem Details responses
  • Opt-in HATEOAS hypermedia links (Richardson Maturity Model Level 3)
  • Hook-based extensibility instead of controller inheritance
  • OpenAPI metadata and package-ready defaults out of the box

Features

Secure by Default

All endpoints require authorization unless you opt out per operation:

app.MapRestLib<Product, Guid>("/api/products", config =>
{
    config.AllowAnonymous(RestLibOperation.GetAll, RestLibOperation.GetById);
    config.RequirePolicy(RestLibOperation.Delete, "AdminOnly");
});

Standards-Compliant Responses

RestLib follows the Zalando REST API Guidelines and uses RFC 9457 Problem Details for error payloads.

  • snake_case JSON properties
  • Cursor-based pagination API (forward-only by design; the EF Core adapter uses keyset cursors for supported stable sorts and falls back to encoded offsets otherwise — see ADR-001)
  • Structured validation and error responses
  • Consistent HTTP status codes
{
  "type": "/problems/not-found",
  "title": "Resource Not Found",
  "status": 404,
  "detail": "Product with ID '999' does not exist.",
  "instance": "/api/products/999"
}

Advanced Filtering

RestLib supports allow-listed query-string filtering for scalar properties and nested reference-property paths. Equality filters use direct query parameters, while range, string, and membership operators use bracket syntax such as price[gte] and name[contains].

For complete fluent setup, operator coverage, nested-path examples, and JSON equivalents, see docs/guides/query-features.md.

Sorting

Sorting uses an allow-list of sortable properties with optional default ordering. Query names use snake_case, directions are asc or desc, and nested reference-property paths use dotted names such as customer.name.

For fluent examples, multi-sort patterns, nested-path rules, and JSON configuration, see docs/guides/query-features.md.

Rate Limiting

Integrate with ASP.NET Core's rate limiting middleware to throttle requests per operation:

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("read-policy", o => { o.PermitLimit = 100; o.Window = TimeSpan.FromMinutes(1); });
    options.AddFixedWindowLimiter("write-policy", o => { o.PermitLimit = 20; o.Window = TimeSpan.FromMinutes(1); });
});

app.UseRateLimiter();

app.MapRestLib<Product, Guid>("/api/products", config =>
{
    config.UseRateLimiting("read-policy", RestLibOperation.GetAll, RestLibOperation.GetById);
    config.UseRateLimiting("write-policy", RestLibOperation.Create, RestLibOperation.Update);
});

Set a default policy and exempt specific operations:

config.UseRateLimiting("default-policy");
config.DisableRateLimiting(RestLibOperation.GetById);

Rate limiting is opt-in. RestLib applies the named policy to endpoints; the application defines and registers the actual policies.

Field Selection

Field selection lets clients request sparse fieldsets such as ?fields=id,name,price instead of returning the full entity every time. Unknown or disallowed fields return a 400 Problem Details response, and nested reference-property paths are supported with snake_case dotted query names.

For full examples, JSON configuration, nested sparse output behavior, and interactions with filtering/sorting/pagination, see docs/guides/query-features.md.

Nested object responses (opt-in)

Sparse field selection defaults to flat dotted keys for backward compatibility, but resources can opt into rebuilt nested objects when that response shape is a better fit for clients. The opt-in applies only to sparse responses; dense fallback projection continues to use flat output.

For the fluent and JSON opt-in shapes plus concrete examples of both response modes, see docs/guides/query-features.md.

Batch Operations

Batch endpoints support create, update, patch, and delete actions over multiple resources in one request. Responses report per-item status, returning 200 when all items succeed and 207 Multi-Status when results are mixed.

For request examples, batch limits, and hook/validation behavior, see docs/guides/extensibility-and-operations.md.

Enable HATEOAS to add CRUD-aware _links metadata to entity responses. RestLib can also merge in custom link relations through IHateoasLinkProvider<TEntity, TKey> when you want to point clients at related resources.

For full response examples and custom link-provider registration, see docs/guides/extensibility-and-operations.md.

Select Operations

RestLib lets you expose only the CRUD operations you want and combine generated endpoints with custom routes when needed. The same resource surface can be declared fluently, loaded from JSON files, or bound from appsettings.json.

For full examples covering operation allow-lists, JSON registration, the new UnifiedTypeResolver, and two-model folder loading, see docs/guides/extensibility-and-operations.md.

Extensible via Hooks

Hooks provide strongly typed pipeline customization without controller inheritance or framework subclassing. JSON can select named hooks per operation while C# keeps the actual behavior implementation and test surface.

For fluent and named-hook examples, see docs/guides/extensibility-and-operations.md.

Persistence-Agnostic

RestLib stays persistence-agnostic: use the in-memory adapter, the EF Core adapter, or your own repository implementation behind the same endpoint surface.

For a custom repository example and guidance on where the EF Core adapter fits, see docs/guides/extensibility-and-operations.md.

EF Core Adapter

The official EF Core adapter wires RestLib over a DbContext without changing your EF Core model ownership. It supports filtering, sorting, counting, pagination, batch operations, and conditional field-selection projection pushdown, with some adapter-specific limits.

For registration examples and the broader adapter overview, see docs/guides/extensibility-and-operations.md. For production migration workflow, see docs/guides/ef-core-migrations.md.

Current EF Core Adapter Limitations
  • At most two key parts - the adapter supports scalar keys and ordered two-part composite keys via RestLibCompositeKey<TFirst, TSecond>. Keys with more than two parts are not supported.
  • Keyset pagination with offset fallback - the EF Core adapter uses last-seen sort values plus the key for supported stable sorts, but falls back to encoded offset cursors for unsupported sort shapes.
  • Projection pushdown is opt-in and conditional - when enabled, EF Core pushes down direct scalar field selections and still includes key/filter/sort columns in SQL. Nested field selections currently fall back to post-fetch projection, and any non-projectable selection still falls back when HATEOAS, ETag, or hooks are active.
  • Nested query paths are reference-only - filtering, sorting, and field selection support dot-separated nested reference-property paths such as customer.email. Collection-valued paths are not supported, nested sparse responses use dotted output keys, and PATCH handling still operates on direct entity properties.
  • Constraint mapping is provider-limited - database constraint classification still relies primarily on exception-message inspection and is not yet specialized per provider.

Use the adapter when you want the standard RestLib endpoint surface over a typical EF Core model, including two-part composite-key resources. Expect to write a custom repository if you need keys with more than two parts, deep/navigational query semantics, or SQL-level projection beyond direct scalar property selection.

Versioning

RestLib integrates with common ASP.NET Core versioning patterns via route groups, including URL prefixes and Asp.Versioning.Http when you need header, query-string, or media-type versioning.

For complete examples of URL-prefix versioning, prefix-less overloads, and Asp.Versioning.Http integration, see docs/guides/extensibility-and-operations.md.

URL prefix versioning
Prefix-less overload on a route group
With Asp.Versioning.Http

Performance

RestLib adds minimal overhead compared to hand-written Minimal APIs. In some cases, it is faster due to optimized code paths.

Operation Raw API RestLib Overhead Memory
GET by ID 170.5 us 217.4 us +27% +4%
GET all 313.4 us 271.7 us -13% +14%
POST 265.5 us 384.8 us +45% +13%
PUT 232.1 us 282.0 us +22% +13%

Benchmarks were run on .NET 10.0.5 with 100 seeded items on Linux (Ubuntu 24.04). Absolute times are higher than typical due to running without process priority elevation — focus on the relative overhead (Ratio column) rather than raw microseconds. Re-run with cd benchmarks/RestLib.Benchmarks && dotnet run -c Release to get numbers for your hardware.

<details> <summary>Full benchmark results</summary>

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Intel Core i3-8130U CPU 2.20GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.201
  [Host]     : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v3
  DefaultJob : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v3

| Method                                      | Categories     | Mean     | Error    | StdDev    | Median   | Ratio | RatioSD | Allocated | Alloc Ratio |
|-------------------------------------------- |--------------- |---------:|---------:|----------:|---------:|------:|--------:|----------:|------------:|
| 'Raw Minimal API - POST'                    | Create         | 265.5 us | 30.76 us |  89.74 us | 260.1 us |  1.12 |    0.56 |  12.46 KB |        1.00 |
| 'RestLib - POST'                            | Create         | 384.8 us | 71.82 us | 203.73 us | 326.7 us |  1.63 |    1.07 |  14.10 KB |        1.13 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'Raw Minimal API - GET all'                 | GetAll         | 313.4 us | 39.61 us | 111.73 us | 285.8 us |  1.12 |    0.54 |  16.74 KB |        1.00 |
| 'RestLib - GET all'                         | GetAll         | 271.7 us | 31.49 us |  92.86 us | 246.1 us |  0.97 |    0.46 |  19.05 KB |        1.14 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'RestLib - GET all (no fields)'             | GetAll_Fields  | 289.6 us | 41.97 us | 121.78 us | 261.4 us |  1.18 |    0.70 |  19.07 KB |        1.00 |
| 'RestLib - GET all (?fields=id,name)'       | GetAll_Fields  | 475.3 us | 51.86 us | 149.62 us | 448.8 us |  1.93 |    0.99 |  39.98 KB |        2.10 |
| 'RestLib - GET all (?fields=id,name,price)' | GetAll_Fields  | 563.9 us | 88.23 us | 255.96 us | 498.5 us |  2.29 |    1.42 |  43.43 KB |        2.28 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'Raw Minimal API - GET by ID'               | GetById        | 170.5 us | 26.94 us |  79.43 us | 149.6 us |  1.26 |    0.93 |   9.76 KB |        1.00 |
| 'RestLib - GET by ID'                       | GetById        | 217.4 us | 29.76 us |  82.47 us | 203.9 us |  1.61 |    1.07 |  10.13 KB |        1.04 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'RestLib - GET by ID (no fields)'           | GetById_Fields | 124.3 us | 14.85 us |  42.84 us | 112.1 us |  1.12 |    0.55 |  10.21 KB |        1.00 |
| 'RestLib - GET by ID (?fields=id,name)'     | GetById_Fields | 162.7 us | 16.38 us |  46.47 us | 149.7 us |  1.46 |    0.65 |  12.22 KB |        1.20 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'Raw Minimal API - PUT'                     | Update         | 232.1 us | 24.69 us |  72.81 us | 221.1 us |  1.10 |    0.51 |  12.78 KB |        1.00 |
| 'RestLib - PUT'                             | Update         | 282.0 us | 37.88 us | 108.06 us | 258.5 us |  1.34 |    0.69 |  14.44 KB |        1.13 |

</details>

Learn More

Guides

Architecture Decisions

Key decisions are documented as Architecture Decision Records:

ADR Decision
ADR-001 Cursor-based pagination over offset
ADR-002 Authorization required by default
ADR-003 Minimal APIs over controllers
ADR-004 snake_case JSON naming
ADR-005 RFC 9457 Problem Details for errors
ADR-006 Operation allowlists and denylists
ADR-007 Hybrid field projection strategy
ADR-008 Batch operations with partial success
ADR-009 Allow-list sorting with default sort
ADR-010 API versioning via route groups
ADR-011 Query parameter filtering
ADR-012 Hook pipeline for extensibility
ADR-013 Filter operators beyond equality
ADR-014 ETag support for caching and concurrency
ADR-015 Data Annotation validation
ADR-016 JSON resource configuration
ADR-017 Rate limiting integration
ADR-018 PATCH JsonElement coupling acknowledgement
ADR-019 HATEOAS hypermedia links
ADR-020 Structured logging
ADR-021 EF Core repository adapter
ADR-022 Per-file JSON resources
ADR-023 JSON validation rules
ADR-024 Two-model resources and the mapping seam
ADR-025 Two-part composite key support

Packages

Package Description NuGet
RestLib Core library RestLib
RestLib.InMemory In-memory repository for testing and prototyping RestLib.InMemory
RestLib.EntityFrameworkCore EF Core repository adapter for production databases RestLib.EntityFrameworkCore

Requirements

  • .NET 10.0 or later
  • ASP.NET Core Minimal APIs

Known Limitations

  • Forward-only cursor pagination — cursors support forward traversal only; there is no backward/previous-page navigation.

  • Cursor contract, adapter-specific implementation — RestLib exposes an opaque cursor API. The InMemory adapter still uses encoded offsets/indexes, while the EF Core adapter uses keyset cursors for supported stable sorts and falls back to offsets otherwise.

  • Field selection pushdown is adapter-dependent — the core endpoint layer can project after retrieval for any repository. The EF Core adapter can opt into conditional SQL projection pushdown for direct scalar fields via EnableProjectionPushdown; nested selections and requests using HATEOAS, ETags, or hooks fall back to materialized projection.

  • Nested query paths are reference-only — filtering, sorting, and field selection support dotted nested reference-property paths such as customer.email, but collection-valued paths are not supported and sparse nested responses use dotted output keys rather than nested objects.

  • Built-in search is intentionally limited — RestLib supports configured OR-of-contains search across string fields, but it does not provide full-text indexing, ranking, fuzzy matching, or provider-specific search features.

  • No CORS configuration — RestLib does not configure CORS. If your API is consumed by browsers, add ASP.NET Core's built-in CORS middleware:

    builder.Services.AddCors(options =>
    {
        options.AddDefaultPolicy(policy => policy
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());
    });
    app.UseCors();
    

    See the ASP.NET Core CORS documentation for production-ready policies.

Contributing

Contributions are welcome. Read the contributing guide.

License

This project is licensed under the MIT License. See the license.

Acknowledgments

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.5.2 93 5/27/2026
2.5.1 94 5/18/2026
2.5.0 108 5/13/2026
2.4.0 91 5/13/2026
2.3.0 113 4/16/2026
2.2.1 105 4/12/2026
2.2.0 107 4/11/2026
2.1.0 104 4/11/2026
2.0.0 108 4/10/2026
1.3.1 101 4/6/2026
1.3.0 101 4/4/2026
1.2.0 103 4/4/2026
1.1.0 106 4/4/2026
1.0.0 102 4/4/2026
0.8.0 104 4/1/2026
0.7.0 106 3/31/2026
0.6.0 103 3/30/2026
0.5.0 102 3/29/2026
0.4.0 101 3/26/2026
0.3.0 112 3/11/2026
Loading failed