Skip to content

ShipMvp/ship

Repository files navigation

ShipMVP Backend — Template

Ultra-lean .NET 9 backend template for building modular SaaS backends fast. Clean Architecture, EF Core + PostgreSQL, .NET Aspire orchestration, and a simple plugin model so you can add features without touching template code.


What this template gives you

  • Modular foundation: IShipMvpModule + reflection loader (no manual wiring).
  • Single database: one AppDbContext that auto-discovers all IEntityTypeConfiguration<> across your modules.
  • Orchestration: optional Aspire AppHost (dashboard, logs, traces, service discovery).
  • Batteries included: Swagger, health, connection resilience, pgAdmin (via Aspire).
  • Read-only by design: consume this repo as a Git submodule; keep your app code separate.

Repository layout (template)

backend/
└─ src/
   ├─ ShipMvp.Abstractions/           # Contracts only (no ASP.NET deps)
   │  └─ IShipMvpModule.cs
   ├─ ShipMvp.Modularity/             # Module loader (DI + endpoint discovery)
   │  └─ ModuleLoader.cs
   ├─ ShipMvp.Infrastructure/         # EF Core DbContext & conventions
   │  └─ AppDbContext.cs              # Scans IEntityTypeConfiguration<> in all loaded assemblies
   └─ ShipMvp.AppHost/ (optional)     # .NET Aspire orchestration (sample runner)

Do not edit template code in consumer apps. Update via submodule bump (see Upgrading).


How to use this template in your app

1) Add as a Git submodule

git submodule add -b stable https://github.com/your-org/shipmvp backend/shipmvp
git submodule update --init --recursive

You’ll have a read-only folder at backend/shipmvp.

2) Create your host API (consumer)

dotnet new webapi -n MyProduct.Api -o apps/backend/MyProduct.Api
dotnet sln add apps/backend/MyProduct.Api/MyProduct.Api.csproj

Reference template projects:

dotnet add apps/backend/MyProduct.Api/MyProduct.Api.csproj reference \
  backend/shipmvp/src/ShipMvp.Infrastructure/ShipMvp.Infrastructure.csproj \
  backend/shipmvp/src/ShipMvp.Modularity/ShipMvp.Modularity.csproj

3) Configure DbContext + module discovery (in your Program.cs)

using Microsoft.EntityFrameworkCore;
using ShipMvp.Infrastructure; // AppDbContext
using ShipMvp.Modularity;    // ModuleLoader

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(opts =>
{
    var cs = builder.Configuration.GetConnectionString("Default")
          ?? "Host=localhost;Port=5432;Database=shipmvp;Username=postgres;Password=ShipMVPPass123!";
    // Put migrations in your app's assembly (or a dedicated *.Migrations project)
    opts.UseNpgsql(cs, b => b.MigrationsAssembly("MyProduct.Migrations"));
});

builder.AddDiscoveredModules(); // auto-register modules found in loaded assemblies
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
app.UseSwagger(); app.UseSwaggerUI();
app.MapGet("/", () => Results.Ok("MyProduct API running"));
app.MapDiscoveredModules();     // auto-map endpoints from modules
app.Run();

4) Keep migrations outside the template

Create a dedicated migrations project in your app and point the context to it.

dotnet new classlib -n MyProduct.Migrations -o apps/backend/MyProduct.Migrations
dotnet sln add apps/backend/MyProduct.Migrations/MyProduct.Migrations.csproj
dotnet add apps/backend/MyProduct.Migrations package Microsoft.EntityFrameworkCore.Design
dotnet add apps/backend/MyProduct.Migrations package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add apps/backend/MyProduct.Migrations/MyProduct.Migrations.csproj reference \
  backend/shipmvp/src/ShipMvp.Infrastructure/ShipMvp.Infrastructure.csproj

Design-time factory (in your API) so EF can scaffold the full model:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using ShipMvp.Infrastructure;

public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
    public AppDbContext CreateDbContext(string[] _)
    {
        var cfg = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: true)
            .AddEnvironmentVariables()
            .Build();

        var cs = cfg.GetConnectionString("Default")
               ?? "Host=localhost;Port=5432;Database=shipmvp;Username=postgres;Password=ShipMVPPass123!";

        return new AppDbContext(
            new DbContextOptionsBuilder<AppDbContext>()
                .UseNpgsql(cs, b => b.MigrationsAssembly("MyProduct.Migrations"))
                .Options);
    }
}

Create/apply migrations:

dotnet ef migrations add Initial \
  --project apps/backend/MyProduct.Migrations \
  --startup-project apps/backend/MyProduct.Api \
  --context AppDbContext

dotnet ef database update \
  --project apps/backend/MyProduct.Migrations \
  --startup-project apps/backend/MyProduct.Api \
  --context AppDbContext

Building modules (your code)

  1. New project: modules/Billing/Billing.csproj

  2. Reference Abstractions from the template:

    dotnet add modules/Billing/Billing.csproj reference backend/shipmvp/src/ShipMvp.Abstractions/ShipMvp.Abstractions.csproj
    dotnet add apps/backend/MyProduct.Api/MyProduct.Api.csproj reference modules/Billing/Billing.csproj
  3. Implement:

    • Entities + IEntityTypeConfiguration<> (use per-module schema, e.g., "billing")
    • IShipMvpModule for DI + endpoints

Interface (from template)

// ShipMvp.Abstractions
public interface IShipMvpModule
{
    void ConfigureServices(IServiceCollection services, IConfiguration config);
    void MapEndpoints(IEndpointRouteBuilder endpoints);
}

Example module (consumer app)

// modules/Billing/Domain/Invoice.cs
public class Invoice
{
    public Guid Id { get; set; }
    public string CustomerName { get; set; } = default!;
    public decimal Amount { get; set; }
    public DateTime CreatedAt { get; set; }
}

// modules/Billing/Infrastructure/InvoiceConfig.cs
public sealed class InvoiceConfig : IEntityTypeConfiguration<Invoice>
{
    public void Configure(EntityTypeBuilder<Invoice> b)
    {
        b.ToTable("Invoices", "billing");
        b.HasKey(x => x.Id);
        b.Property(x => x.CustomerName).HasMaxLength(200).IsRequired();
        b.Property(x => x.Amount).HasColumnType("numeric(18,2)");
        b.Property(x => x.CreatedAt).HasDefaultValueSql("now()");
    }
}

// modules/Billing/BillingModule.cs
public sealed class BillingModule : IShipMvpModule
{
    public void ConfigureServices(IServiceCollection services, IConfiguration config) { }

    public void MapEndpoints(IEndpointRouteBuilder endpoints)
    {
        var g = endpoints.MapGroup("/api/billing/invoices");

        g.MapGet("/", async (AppDbContext db) =>
            await db.Set<Invoice>().OrderByDescending(x => x.CreatedAt).ToListAsync());

        g.MapPost("/", async (AppDbContext db, Invoice dto) =>
        {
            dto.Id = Guid.NewGuid();
            dto.CreatedAt = DateTime.UtcNow;
            db.Add(dto);
            await db.SaveChangesAsync();
            return Results.Created($"/api/billing/invoices/{dto.Id}", dto);
        });
    }
}

No manual wiring: the template’s ModuleLoader discovers and maps modules automatically.


Running with .NET Aspire (optional)

The template includes an AppHost example to orchestrate Postgres, pgAdmin, API, and the Aspire dashboard.

  • Start:

    dotnet run --project backend/src/ShipMvp.AppHost/ShipMvp.AppHost.csproj
  • Access:

    • Aspire Dashboard: https://localhost:17152
    • API Swagger: http://localhost:5000/swagger
    • pgAdmin: via service discovery link in the dashboard

In consumer apps, keep using your own API project; Aspire is optional but handy in development.


Upgrading the template in your app

This repo should be a submodule in your consumer project. Update it like this:

git -C backend/shipmvp fetch --tags origin
git -C backend/shipmvp checkout stable   # or a specific tag, e.g., v0.4.0
git add backend/shipmvp
git commit -m "chore(shipmvp): bump backend template"

(Recommend a CI guard to prevent edits within backend/shipmvp/*.)


Troubleshooting

  • “No model changes detected” when adding entities Ensure your API references the module project so its assembly loads; EF configs must implement IEntityTypeConfiguration<>.

  • Unexpected DROP operations in migrations Removing a module will look like drops to EF. Review the generated migration before applying; delete unintended operations.

  • DB connection issues Verify Postgres is running, connection string is injected, and the container is ready (check logs).


License

This repository is licensed under the Apache License 2.0. See the LICENSE file at the repository root for details.

License: Apache-2.0


Ship fast, stay modular. This backend template is your foundation; put all of your product logic into your own modules and projects—keep the template clean and updatable.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors