Skip to content

Your First API

In this first step, you will create a new .NET project, define a Granit module, add a domain model, and expose a working endpoint — all with zero manual DI configuration.

Open a terminal and scaffold a new Minimal API project:

Terminal window
dotnet new web -n TaskManagement.Api
cd TaskManagement.Api

Add the two foundational Granit packages:

Terminal window
dotnet add package Granit.Core
dotnet add package Granit.Timing

Granit.Core provides the module system and shared domain types. Granit.Timing provides IClock, a testable abstraction over DateTimeOffset.UtcNow.

Every Granit application has a root module. Modules declare their dependencies via the [DependsOn] attribute — the framework resolves the full dependency graph at startup.

Create TaskManagementModule.cs in the project root:

using Granit.Core.Modularity;
using Granit.Timing;
namespace TaskManagement.Api;
[DependsOn(typeof(GranitTimingModule))]
public sealed class TaskManagementModule : GranitModule
{
}

This single attribute tells Granit to load GranitTimingModule (and its own transitive dependencies) before your module. No services.AddSingleton<IClock>(...) call needed.

Replace the generated Program.cs with:

using Granit.Timing;
using TaskManagement.Api;
var builder = WebApplication.CreateBuilder(args);
await builder.AddGranitAsync<TaskManagementModule>();
var app = builder.Build();
app.UseGranit();
app.MapGet("/", (IClock clock) => new
{
Message = "Task Management API",
CurrentTime = clock.Now
});
app.Run();

AddGranitAsync<T>() walks the module dependency graph, calls each module’s ConfigureServices in order, and registers everything into the standard IServiceCollection. UseGranit() does the same for middleware.

Terminal window
dotnet run

Open http://localhost:5000 (or whichever port your console shows). You should see a JSON response:

{
"message": "Task Management API",
"currentTime": "2026-03-13T10:42:00+00:00"
}

Create a Domain folder and add TaskItem.cs:

using Granit.Core.Domain;
namespace TaskManagement.Api.Domain;
public sealed class TaskItem : AuditedEntity
{
public string Title { get; set; } = string.Empty;
public string? Description { get; set; }
public bool IsCompleted { get; set; }
public DateTimeOffset? DueDate { get; set; }
}

AuditedEntity inherits from Entity and gives you five fields out of the box: Id (Guid), CreatedAt, CreatedBy, ModifiedAt, and ModifiedBy. The persistence layer fills these automatically via EF Core interceptors — you never set them manually.

Granit provides a hierarchy of base classes. Pick the one that matches your requirements:

Base classFields addedUse case
EntityIdSimple entities with no audit needs
CreationAuditedEntity+ CreatedAt, CreatedByWrite-once records (logs, events)
AuditedEntity+ ModifiedAt, ModifiedByMost business entities
FullAuditedEntity+ IsDeleted, DeletedAt, DeletedByGDPR-compliant soft delete

Now expose a basic in-memory endpoint so you can verify the domain model compiles and serializes correctly. Add the following to Program.cs, before app.Run():

app.MapGet("/tasks/sample", (IClock clock) =>
{
var task = new TaskManagement.Api.Domain.TaskItem
{
Title = "Write documentation",
Description = "Create the Getting Started tutorial",
DueDate = clock.Now.AddDays(7)
};
return Results.Ok(task);
});

Run the application again and visit /tasks/sample. You will see the TaskItem serialized as JSON, including the audit fields (which are default values for now — they will be populated once we add persistence).

In roughly 30 lines of code, you have:

  • A modular application with dependency resolution
  • A testable time abstraction (no DateTime.Now anywhere)
  • A domain entity with built-in audit fields
  • A working HTTP endpoint

The module system is the foundation of every Granit application. Each package you add later (persistence, authentication, observability) follows the same pattern: add the NuGet package, add a [DependsOn] attribute, and the module registers its services.

The in-memory sample endpoint is useful for testing, but real applications need a database. In the next step, you will add EF Core with PostgreSQL and get automatic audit trail population.

Adding Persistence