Guides.NET
ASP.NET Core with PostgreSQL and EF Core
Create a .NET API project with ASP.NET Core Minimal APIs, PostgreSQL, EF Core, ASP.NET Identity, SignalR, Serilog, and xUnit using Better Fullstack.
Updated August 16, 2026
Use this stack when you want a .NET HTTP API with relational persistence, structured logging, and a test project wired up from the first commit.
npm create better-fullstack@latest my-dotnet-api -- \ --ecosystem dotnet \ --dotnet-web-framework aspnet-minimal \ --dotnet-orm ef-core \ --dotnet-auth aspnet-identity \ --dotnet-api minimal-api \ --dotnet-testing xunit \ --dotnet-realtime signalr \ --dotnet-observability serilog \ --dotnet-validation fluentvalidation \ --dotnet-deploy docker \ --database postgresWhat this creates
- An ASP.NET Core application using Minimal APIs.
- PostgreSQL through the Npgsql EF Core provider.
- ASP.NET Core Identity registered against the application
DbContext. - A SignalR hub for server-pushed updates.
- Serilog console logging.
- FluentValidation rules and an xUnit test project.
Generated shape
The scaffold is a single ASP.NET Core project plus a sibling test project. The project name and namespace come from the directory name with every character outside A-Za-z0-9_ replaced by an underscore, so my-dotnet-api produces my_dotnet_api.
my-dotnet-api/
├── my_dotnet_api.csproj
├── my_dotnet_api.http
├── Program.cs
├── appsettings.json
├── appsettings.Development.json
├── Dockerfile
├── Validators/
│ └── SampleInputValidator.cs
└── my_dotnet_api.Tests/
├── my_dotnet_api.Tests.csproj
└── ApiTests.csEverything lives in Program.cs: endpoint mapping, service registration, the TodoItem model, and AppDbContext. Split it into folders once the API grows past the generated sample.
Prerequisites
- .NET SDK 10 or newer.
- A PostgreSQL instance reachable from the machine running the app.
- Docker if you keep the generated
Dockerfilein your deploy path.
Run and verify
cd my-dotnet-api
dotnet restore
dotnet runThe generated appsettings.json ships a local development connection string. Override it for anything else through ConnectionStrings__DefaultConnection rather than editing the committed file.
export ConnectionStrings__DefaultConnection="Host=localhost;Port=5432;Database=my_dotnet_api;Username=postgres;Password=postgres"Open the URL printed by ASP.NET Core, then use the generated .http file to exercise the endpoints.
No solution file is generated, and the test SDK lives only in the test project, so a bare dotnet test from the project root builds the API and runs nothing. Name the test project explicitly:
dotnet build
dotnet test my_dotnet_api.Tests/my_dotnet_api.Tests.csprojCreate the schema before calling the data endpoints
The generated Program.cs registers AppDbContext but never calls Migrate() or EnsureCreated(), so /api/todos hits an empty database until you create the schema yourself. Microsoft.EntityFrameworkCore.Design is already referenced, so the EF tooling works without adding packages:
dotnet tool install --global dotnet-ef
dotnet ef migrations add InitialCreate
dotnet ef database updateRun migrations against production before routing traffic. Treat this as the first change you make to a fresh scaffold.
Representative snippets
The persisted model and context are plain EF Core.
public sealed class TodoItem
{
public int Id { get; set; }
public required string Title { get; set; }
public bool Complete { get; set; }
}
public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<TodoItem> Todos => Set<TodoItem>();
}Minimal API endpoints take the context by injection.
app.MapGet("/api/todos", async (AppDbContext db) =>
{
return Results.Ok(await db.Todos.OrderBy(todo => todo.Id).ToListAsync());
});
app.MapPost("/api/todos", async (TodoInput input, AppDbContext db) =>
{
var todo = new TodoItem { Title = input.Title, Complete = false };
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/api/todos/{todo.Id}", todo);
});The xUnit project boots the real application through WebApplicationFactory, so tests exercise the actual pipeline rather than a mock.
public sealed class ApiTests : IClassFixture<WebApplicationFactory<Program>>
{
[Fact]
public async Task RootEndpointReturnsOk()
{
using var client = _factory.CreateClient();
using var response = await client.GetAsync("/");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}When to choose it
Choose .NET when you want a compiled, statically typed backend with first-party database, identity, logging, and realtime libraries maintained on one release cadence.
Choose Minimal APIs over --dotnet-web-framework aspnet-mvc when the service is API-only. Choose MVC when you want controllers, model binding conventions, and view support.
Compatibility notes
-
--dotnet-orm ef-coreselects the provider from--database: Npgsql forpostgres,Microsoft.EntityFrameworkCore.Sqliteforsqlite. -
--dotnet-auth aspnet-identitycallsAddIdentityCore<IdentityUser>().AddEntityFrameworkStores<AppDbContext>(), but the generatedAppDbContextderives fromDbContext, notIdentityDbContext. Change the base class and generate a migration before resolving the user store. -
--dotnet-validation fluentvalidationgeneratesSampleInputValidatorbut does not register it, and the csproj references only the coreFluentValidationpackage. Assembly scanning needs the DI extensions package first:bashdotnet add package FluentValidation.DependencyInjectionExtensionsThen
builder.Services.AddValidatorsFromAssemblyContaining<SampleInputValidator>()resolves. Without that package the call does not compile, whatever the generated file's own comment suggests. -
Deployment targets for .NET come from
--dotnet-deploy, not the TypeScript--web-deployand--server-deployflags.
Deployment notes
The generated Dockerfile builds and publishes the app. The scaffold reads PORT and binds all interfaces when ASPNETCORE_URLS is unset, so container platforms that inject a port work without changes.
Before deploying, set the production connection string, apply migrations, and confirm Serilog's sink configuration matches where you collect logs.
Troubleshooting
| Symptom | Check |
|---|---|
relation "Todos" does not exist | Create and apply an EF migration; nothing creates the schema at startup. |
| App binds an unexpected port | PORT is only honored when ASPNETCORE_URLS is unset. |
| Identity user store fails to resolve | AppDbContext needs to derive from IdentityDbContext and have a migration. |
| Validators never run | Add FluentValidation.DependencyInjectionExtensions, then register them with AddValidatorsFromAssemblyContaining. |
| Connection string changes are ignored | Real environment variables win over the local .env the app loads at startup. |
Tradeoffs
A single Program.cs keeps the scaffold readable but stops scaling once you add more than a handful of endpoints. Plan the split into endpoint groups and a separate data project early.
Next steps
- Open the Stack Builder.
- Read the .NET ecosystem docs.
- Review the CLI create reference.