Ninjadog 1.0.2
See the version list below for details.
dotnet tool install --global Ninjadog --version 1.0.2
dotnet new tool-manifest
dotnet tool install --local Ninjadog --version 1.0.2
#tool dotnet:?package=Ninjadog&version=1.0.2
nuke :add-package Ninjadog --version 1.0.2
<p align="center"> <img src="logo.png" alt="Ninjadog logo" width="256" /> </p>
Ninjadog
One attribute. Full REST API. Zero boilerplate.
Quick Start
dotnet new web -n MyApi && cd MyApi
dotnet add package Ninjadog
[Ninjadog]
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
dotnet build && dotnet run
That's it. You now have a full CRUD API with endpoints, DTOs, validation, repositories, services, mappers, and OpenAPI docs.
Table of Contents
- The Problem
- Why Ninjadog?
- What Gets Generated
- Generated Output Examples
- Features
- Tech Stack
- Getting Started
- Usage
- Architecture
- Project Structure
- Generators
- CLI
- Roadmap
- Contributing
- License
The Problem
Writing boilerplate C# code for REST APIs is repetitive and error-prone — DTOs, mappers, validators, repositories, endpoints, API clients. Developers waste hours on mechanical plumbing code that follows predictable patterns, and every layer must stay in sync whenever the domain model changes.
Why Ninjadog?
| Without Ninjadog | With Ninjadog | |
|---|---|---|
| Code you write | ~500+ lines per entity | ~10 lines per entity |
| Files to maintain | 20+ per entity | 1 per entity |
| Layers in sync | Manual | Automatic |
| Runtime cost | Depends on approach | Zero (compile-time) |
| Reflection | Often required | None |
| Time to full CRUD | Hours | Seconds |
Ninjadog uses C# Source Generators to produce your entire API stack at compile time. No runtime reflection, no code-gen CLI step, no files to keep in sync. Change your entity, rebuild, done.
What Gets Generated
For each entity annotated with [Ninjadog], the generator produces:
| Category | Generated Files | Description |
|---|---|---|
| Endpoints | 5 | Create, GetAll (paginated), GetOne, Update, Delete |
| Contracts | 7 | DTOs, request objects, response objects |
| Data Layer | 4 | Repository + interface, service + interface |
| Mapping | 4 | Domain-to-DTO, DTO-to-Domain, Domain-to-Contract, Contract-to-Domain |
| Validation | 2 | Create + Update request validators |
| OpenAPI | 5 | Summaries for each endpoint |
| Database | 2 | Initializer + connection factory |
| Clients | 2 | C# and TypeScript API clients |
| Total | ~30 files | From a single annotated class |
Generated HTTP Endpoints
POST /products Create a new product
GET /products List all products (paginated: ?page=1&pageSize=10)
GET /products/{id:guid} Get a single product
PUT /products/{id:guid} Update a product
DELETE /products/{id:guid} Delete a product
Route constraints are dynamic — :guid, :int, or untyped — based on your entity's key type.
Generated Output Examples
All examples below are real generated code from Ninjadog's verified snapshot tests.
<details> <summary><strong>Endpoint — GetAll with pagination</strong></summary>
public partial class GetAllTodoItemsEndpoint
: EndpointWithoutRequest<GetAllTodoItemsResponse>
{
public ITodoItemService TodoItemService { get; private set; } = null!;
public override void Configure()
{
Get("/todo-items");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken ct)
{
var page = int.TryParse(HttpContext.Request.Query["page"], out var p) && p > 0 ? p : 1;
var pageSize = int.TryParse(HttpContext.Request.Query["pageSize"], out var ps) && ps > 0 ? ps : 10;
var (todoItems, totalCount) = await TodoItemService.GetAllAsync(page, pageSize);
var todoItemsResponse = todoItems.ToTodoItemsResponse(page, pageSize, totalCount);
await SendOkAsync(todoItemsResponse, ct);
}
}
</details>
<details> <summary><strong>Endpoint — GetOne with route constraint</strong></summary>
public partial class GetTodoItemEndpoint
: Endpoint<GetTodoItemRequest, TodoItemResponse>
{
public ITodoItemService TodoItemService { get; private set; } = null!;
public override void Configure()
{
Get("/todo-items/{id:guid}");
AllowAnonymous();
}
public override async Task HandleAsync(GetTodoItemRequest req, CancellationToken ct)
{
var todoItem = await TodoItemService.GetAsync(req.Id);
if (todoItem is null)
{
await SendNotFoundAsync(ct);
return;
}
var todoItemResponse = todoItem.ToTodoItemResponse();
await SendOkAsync(todoItemResponse, ct);
}
}
</details>
<details> <summary><strong>Validator — type-aware (skips value types)</strong></summary>
public partial class CreateTodoItemRequestValidator : Validator<CreateTodoItemRequest>
{
public CreateTodoItemRequestValidator()
{
RuleFor(x => x.Title)
.NotEmpty()
.WithMessage("Title is required!");
RuleFor(x => x.Description)
.NotEmpty()
.WithMessage("Description is required!");
RuleFor(x => x.DueDate)
.NotEmpty()
.WithMessage("DueDate is required!");
// IsCompleted (bool), Priority (int), Cost (decimal) — skipped, value types always have defaults
}
}
</details>
<details> <summary><strong>Database — SQLite schema with type-aware columns</strong></summary>
public partial class DatabaseInitializer(IDbConnectionFactory connectionFactory)
{
public async Task InitializeAsync()
{
using var connection = await connectionFactory.CreateConnectionAsync();
await connection.ExecuteAsync(@"CREATE TABLE IF NOT EXISTS TodoItems (
Id CHAR(36) PRIMARY KEY,
Title TEXT NOT NULL,
Description TEXT NOT NULL,
IsCompleted INTEGER NOT NULL,
DueDate TEXT NOT NULL,
Priority INTEGER NOT NULL,
Cost REAL NOT NULL)");
}
}
</details>
Features
- Full CRUD generation — Create, Read All (paginated), Read One, Update, Delete
- API clients — C# client via
/cs-client, TypeScript client via/ts-client - Type-aware validation — Value types (
int,bool,decimal) skip.NotEmpty()rules automatically - Dynamic entity keys — Supports
Guid,int,stringkeys with any property name (not hardcoded toId) - Type-aware database schema — SQLite columns map correctly (
INTEGER,REAL,TEXT,CHAR(36)) - Dynamic route constraints — Routes use
:guid,:int, etc. based on key type - Pagination —
?page=1&pageSize=10withTotalCountmetadata in responses - OpenAPI summaries — Each endpoint gets Swagger documentation
- Database initializer — Schema creation and connection factory generation
- CLI tooling — Project scaffolding and code generation commands
- Snapshot tested — 14 Verify snapshot tests cover template output correctness
Tech Stack
| Layer | Technology |
|---|---|
| Runtime | .NET 10, C# 13 |
| Code Generation | Roslyn Source Generators |
| API Framework | FastEndpoints |
| Database | SQLite + Dapper |
| Validation | FluentValidation |
| OpenAPI | FastEndpoints.Swagger |
| Client Generation | FastEndpoints.ClientGen |
| Architecture | Domain-Driven Design (DDD) |
| CLI | Spectre.Console |
Getting Started
Prerequisites
- .NET 10 SDK or later
Installation
Option 1 — Global CLI tool (recommended)
dotnet tool install -g Ninjadog.CLI
Option 2 — NuGet Package (for library use)
dotnet add package Ninjadog
Option 3 — From Source
git clone https://github.com/Atypical-Consulting/ninjadog.git
cd ninjadog
dotnet build
Usage
Basic Example
- Create a new .NET 10 Web API project:
dotnet new web -n MyApi
cd MyApi
dotnet add package Ninjadog
- Define your domain entity with the
[Ninjadog]attribute:
using Ninjadog;
[Ninjadog]
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
- Build and run — all REST endpoints, contracts, repositories, services, mappers, and validators are generated automatically:
dotnet build
dotnet run
Your API is now live with full CRUD endpoints for Product.
Multiple Entities
Each entity gets its own isolated set of generated files:
[Ninjadog]
public class Movie
{
public Guid Id { get; set; }
public string Title { get; set; }
public int Year { get; set; }
}
[Ninjadog]
public class Order
{
public int OrderId { get; set; } // int key — routes use :int constraint
public string CustomerName { get; set; }
public decimal Total { get; set; }
}
Architecture
┌───────────────────────────────────────────────┐
│ Your C# Source │
│ [Ninjadog] Domain Entities │
└──────────────────┬────────────────────────────┘
│
▼
┌───────────────────────────────────────────────┐
│ Roslyn Compiler │
│ Ninjadog Source Generators │
└──────────────────┬────────────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Contracts │ │ Data │ │ Clients │
│ Requests │ │ DTOs │ │ C# /TS │
│Responses │ │ Mappers │ │ │
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Endpoints │ │ Repos │ │Validators│
│ CRUD │ │ Services │ │ OpenAPI │
└──────────┘ └──────────┘ └──────────┘
│
▼
┌───────────────────────────────────────────────┐
│ Compiled Assembly │
│ Full REST API — Zero Boilerplate │
└───────────────────────────────────────────────┘
Project Structure
ninjadog/
├── src/
│ ├── library/ # Core generator libraries
│ │ ├── Ninjadog.Engine/ # Main source generator engine
│ │ ├── Ninjadog.Engine.Core/ # Core generator abstractions
│ │ ├── Ninjadog.Engine.Infrastructure/ # Infrastructure utilities
│ │ ├── Ninjadog.Helpers/ # Shared helper functions
│ │ ├── Ninjadog.Settings/ # Generator configuration
│ │ └── Ninjadog.Settings.Extensions/ # Settings extension methods
│ ├── tools/
│ │ └── Ninjadog.CLI/ # Command-line interface
│ ├── templates/
│ │ └── Ninjadog.Templates.CrudWebApi/ # CRUD Web API template
│ └── tests/
│ └── Ninjadog.Tests/ # Snapshot + unit tests
├── doc/ # Generator documentation
├── Ninjadog.sln # Solution file
└── global.json # .NET SDK version config
Generators
Ninjadog includes 30 generators organized into 11 categories. Each generator produces either a single shared file or a per-entity file.
| Category | Generators | Scope |
|---|---|---|
| Core | NinjadogGenerator | Single file |
| Contracts — Data | DtoGenerator | Per entity |
| Contracts — Requests | Create, Delete, Get, Update | Per entity |
| Contracts — Responses | GetAllResponse, Response | Per entity |
| Database | DatabaseInitializer, DbConnectionFactory | Single file |
| Endpoints | Create, Delete, GetAll, Get, Update | Per entity |
| Mapping | ApiContract-to-Domain, Domain-to-ApiContract, Domain-to-Dto, Dto-to-Domain | Single file |
| Repositories | Repository, RepositoryInterface | Per entity |
| Services | Service, ServiceInterface | Per entity |
| Summaries | Create, Delete, GetAll, Get, Update | Per entity |
| Validation | CreateRequestValidator, UpdateRequestValidator | Per entity |
Full documentation for each generator is available in doc/generators/.
CLI
Install the CLI as a global dotnet tool:
dotnet tool install -g Ninjadog.CLI
Available commands:
ninjadog init # Initialize a new Ninjadog project
ninjadog build # Build and run the generator engine
ninjadog ninjadog # Generate a new Ninjadog project
Roadmap
- Solution that compiles
- Branding — Name
- Type-aware code generation
- Dynamic entity key support
- Pagination support
- CLI build command
- Template snapshot tests
- CI/CD pipeline
- Branding — Logo
- Branding — Tagline
- Benefits of the solution
- Target audience definition
- Write documentation
- A client demo
- NuGet package publishing
Want to contribute? Pick any roadmap item and open a PR!
Contributing
Contributions are welcome! Here's how to get started:
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run the tests to make sure everything works:
dotnet test - Commit using conventional commits (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
git clone https://github.com/Atypical-Consulting/ninjadog.git
cd ninjadog
dotnet build
dotnet test
Where to Look
- Generator templates —
src/library/Ninjadog.Engine/ - Snapshot tests —
src/tests/Ninjadog.Tests/Templates/ - CLI commands —
src/tools/Ninjadog.CLI/ - Generator docs —
doc/generators/
License
This project is licensed under the Apache License 2.0 — see the LICENSE file for details.
Built with care by Atypical Consulting — opinionated, production-grade open source.
| Product | Versions 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. |
This package has no dependencies.