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.
1. Create the project
Section titled “1. Create the project”Open a terminal and scaffold a new Minimal API project:
dotnet new web -n TaskManagement.Apicd TaskManagement.ApiAdd the two foundational Granit packages:
dotnet add package Granit.Coredotnet add package Granit.TimingGranit.Core provides the module system and shared domain types. Granit.Timing provides IClock, a testable abstraction over DateTimeOffset.UtcNow.
2. Create the root module
Section titled “2. Create the root module”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.
3. Wire into Program.cs
Section titled “3. Wire into Program.cs”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.
4. Run it
Section titled “4. Run it”dotnet runOpen 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"}5. Define the domain model
Section titled “5. Define the domain model”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.
Entity hierarchy
Section titled “Entity hierarchy”Granit provides a hierarchy of base classes. Pick the one that matches your requirements:
| Base class | Fields added | Use case |
|---|---|---|
Entity | Id | Simple entities with no audit needs |
CreationAuditedEntity | + CreatedAt, CreatedBy | Write-once records (logs, events) |
AuditedEntity | + ModifiedAt, ModifiedBy | Most business entities |
FullAuditedEntity | + IsDeleted, DeletedAt, DeletedBy | GDPR-compliant soft delete |
6. Add a first endpoint
Section titled “6. Add a first endpoint”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).
7. What just happened
Section titled “7. What just happened”In roughly 30 lines of code, you have:
- A modular application with dependency resolution
- A testable time abstraction (no
DateTime.Nowanywhere) - 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.
Next step
Section titled “Next step”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.