Ninjadog 1.0.2

There is a newer version of this package available.
See the version list below for details.
dotnet tool install --global Ninjadog --version 1.0.2
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Ninjadog --version 1.0.2
                    
This package contains a .NET tool you can call from the shell/command line.
#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.

Atypical-Consulting - ninjadog .NET 10 stars - ninjadog forks - ninjadog

License GitHub tag issues - ninjadog GitHub pull requests GitHub last commit


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

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, string keys with any property name (not hardcoded to Id)
  • 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=10 with TotalCount metadata 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

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

  1. Create a new .NET 10 Web API project:
dotnet new web -n MyApi
cd MyApi
dotnet add package Ninjadog
  1. 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; }
}
  1. 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:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Run the tests to make sure everything works:
    dotnet test
    
  4. Commit using conventional commits (git commit -m 'feat: add amazing feature')
  5. Push to the branch (git push origin feature/amazing-feature)
  6. 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 templatessrc/library/Ninjadog.Engine/
  • Snapshot testssrc/tests/Ninjadog.Tests/Templates/
  • CLI commandssrc/tools/Ninjadog.CLI/
  • Generator docsdoc/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.

Contributors

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
1.3.0 86 3/12/2026
1.2.1 85 3/11/2026
1.2.0 75 3/11/2026
1.1.1 85 3/11/2026
1.1.0 77 3/11/2026
1.0.4 81 3/11/2026
1.0.2 80 3/10/2026
1.0.1 78 3/10/2026
1.0.0 84 3/10/2026