Getting Started

Get a full CRUD REST API running in under 2 minutes.

Table of contents
  1. Prerequisites
  2. Installation
    1. Option 1 – CLI tool (recommended)
    2. Option 2 – From Source
  3. Your First API
    1. Step 1 – Initialize a new project
    2. Step 2 – Define your domain entities
    3. Step 3 – Validate your config
    4. Step 4 – Generate the code
    5. Step 5 – Run the API
    6. Step 6 – Verify it works
  4. Multiple Entities
  5. CORS Configuration
    1. CORS Options
    2. Generated CORS Policy
  6. What Happens Under the Hood
  7. Troubleshooting
    1. .NET SDK version mismatch
    2. Missing ninjadog.json
    3. Port already in use
    4. Generated project does not compile
    5. ninjadog command not found
  8. Next Steps

Prerequisites

Verify your SDK version with dotnet --version. You need 10.0 or higher.

Installation

Install the Ninjadog CLI as a global .NET tool:

dotnet tool install -g Ninjadog

Option 2 – From Source

git clone https://github.com/Atypical-Consulting/Ninjadog.git
cd ninjadog
dotnet build

Your First API

Follow these steps to create a fully functional REST API from a simple JSON configuration.

Step 1 – Initialize a new project

mkdir MyApi && cd MyApi
ninjadog init

The CLI walks you through a series of interactive prompts (project name, version, description, root namespace, and output path). Press Enter at each prompt to accept the defaults, or type your own values.

Once complete, a ninjadog.json configuration file is created with a sample Person entity (Id, FirstName, LastName, BirthDate) to get you started.

See the CLI Reference – ninjadog init for the full list of prompts and their default values.

Step 2 – Define your domain entities

Open ninjadog.json and edit it to define the entities you need. For example, replace the default content with a Product entity:

{
  "$schema": "./ninjadog.schema.json",
  "config": {
    "name": "MyApi",
    "version": "1.0.0",
    "description": "My API",
    "rootNamespace": "MyApi",
    "outputPath": "src/applications/MyApi",
    "saveGeneratedFiles": true
  },
  "entities": {
    "Product": {
      "properties": {
        "Id": { "type": "Guid", "isKey": true },
        "Name": { "type": "string" },
        "Price": { "type": "decimal" }
      }
    }
  }
}

You can also use ninjadog ui to visually build your configuration in a web browser.

Step 3 – Validate your config

Before generating code, check your configuration for errors:

ninjadog validate

If there are issues, the validator reports them with error codes and descriptions. Fix any errors before proceeding.

Step 4 – Generate the code

ninjadog build

Ninjadog generates ~33 files to disk including endpoints, DTOs, validators, repositories, services, mappers, domain entities, a database initializer, and a complete project structure (.sln, .csproj, Program.cs, appsettings.json). All files are written to the outputPath directory specified in your configuration.

Step 5 – Run the API

Navigate to the generated project and start it:

cd src/applications/MyApi
dotnet run

Step 6 – Verify it works

Open your browser or use curl to test the endpoints:

# Create a product
curl -X POST http://localhost:5000/products \
  -H "Content-Type: application/json" \
  -d '{"name": "Widget", "price": 9.99}'

# List all products (paginated)
curl http://localhost:5000/products

# Get a single product
curl http://localhost:5000/products/{id}

# Update a product
curl -X PUT http://localhost:5000/products/{id} \
  -H "Content-Type: application/json" \
  -d '{"name": "Updated Widget", "price": 12.99}'

# Delete a product
curl -X DELETE http://localhost:5000/products/{id}

You should see JSON responses for each operation. The GetAll endpoint returns paginated results with totalCount metadata.

Multiple Entities

Each entity gets its own isolated set of generated files. Add as many entities as you need in ninjadog.json:

{
  "$schema": "./ninjadog.schema.json",
  "config": {
    "name": "MyApi",
    "version": "1.0.0",
    "description": "My API",
    "rootNamespace": "MyApi",
    "outputPath": "src/applications/MyApi",
    "saveGeneratedFiles": true
  },
  "entities": {
    "Product": {
      "properties": {
        "Id": { "type": "Guid", "isKey": true },
        "Name": { "type": "string" },
        "Price": { "type": "decimal" }
      }
    },
    "Movie": {
      "properties": {
        "Id": { "type": "Guid", "isKey": true },
        "Title": { "type": "string" },
        "Year": { "type": "int" }
      }
    },
    "Order": {
      "properties": {
        "OrderId": { "type": "int", "isKey": true },
        "CustomerName": { "type": "string" },
        "Total": { "type": "decimal" }
      }
    }
  }
}

Then regenerate with ninjadog build. Each entity produces its own full set of CRUD files.

Route constraints are dynamic – :guid, :int, or untyped – based on your entity’s key type. An int key produces /orders/{id:int}, while a Guid key produces /movies/{id:guid}.

CORS Configuration

By default, the generated API allows requests from https://localhost:7270. You can customize the CORS policy by adding a cors block inside config in your ninjadog.json:

{
  "config": {
    "name": "MyApi",
    "version": "1.0.0",
    "description": "My API",
    "rootNamespace": "MyApi",
    "outputPath": "src/applications/MyApi",
    "saveGeneratedFiles": true,
    "cors": {
      "origins": ["https://myapp.com", "https://staging.myapp.com"],
      "methods": ["GET", "POST", "PUT", "DELETE"],
      "headers": ["Content-Type", "Authorization"]
    }
  },
  "entities": {
    "Product": {
      "properties": {
        "Id": { "type": "Guid", "isKey": true },
        "Name": { "type": "string" },
        "Price": { "type": "decimal" }
      }
    }
  }
}

CORS Options

Option Type Required Description
origins string[] Yes The allowed origins for cross-origin requests.
methods string[] No The allowed HTTP methods. When omitted, all methods are allowed.
headers string[] No The allowed request headers. When omitted, all headers are allowed.

If you omit the cors block entirely, Ninjadog defaults to allowing https://localhost:7270 as the only origin – useful for local development without extra configuration.

Generated CORS Policy

When you provide a cors configuration, the generated Program.cs includes a named CORS policy that is registered and applied automatically:

services.AddCors(options =>
{
    options.AddPolicy(name: myAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://myapp.com", "https://staging.myapp.com")
                .WithMethods("GET", "POST", "PUT", "DELETE")
                .WithHeaders("Content-Type", "Authorization");
        });
});

// ...

app.UseCors(myAllowSpecificOrigins);

What Happens Under the Hood

When you run ninjadog build, the CLI reads your ninjadog.json configuration and generates a complete .NET web API project to disk:

What Example for Product
Project .sln, .csproj, Program.cs with AddNinjadog() and UseNinjadog()
Endpoints CreateProductEndpoint, GetAllProductsEndpoint, etc. (FastEndpoints)
Contracts CreateProductRequest, ProductResponse, ProductDto
Validation CreateProductRequestValidator with FluentValidation rules
Data Layer IProductRepository, ProductRepository (Dapper + SQLite), IProductService, ProductService
Mapping Extension methods like .ToProductResponse(), .ToProductDto()
Database DatabaseInitializer with SQLite CREATE TABLE for all entities

The generated code uses primary constructor injection, FastEndpoints for routing, Dapper with SQLite for data access, and FluentValidation for request validation.


Troubleshooting

.NET SDK version mismatch

If you see an error like The framework 'Microsoft.NETCore.App', version '10.0.0' was not found, your .NET SDK is too old.

dotnet --version

Ninjadog requires .NET 10 SDK or later. Download it from dotnet.microsoft.com. If you have multiple SDKs installed, check that a global.json in a parent directory is not pinning an older version.

Missing ninjadog.json

If ninjadog build reports that it cannot find a configuration file, make sure you are running the command from the directory that contains ninjadog.json.

# Verify the file exists
ls ninjadog.json

# If missing, initialize a new project
ninjadog init

You can also run ninjadog validate to check whether the file is present and well-formed. See the CLI Reference for details.

Port already in use

If dotnet run fails with Address already in use or Failed to bind to address, another process is occupying the default port.

# Find what is using port 5000
lsof -i :5000

# Option 1 -- stop the other process
kill <PID>

# Option 2 -- run on a different port
dotnet run --urls "http://localhost:5050"

Generated project does not compile

If the generated code fails to build, check the following:

  1. Outdated generation – Re-run ninjadog build after every change to ninjadog.json.
  2. Invalid property types – Make sure all type values are supported types. Run ninjadog validate to catch type errors.
  3. Duplicate entity names – Entity names must be unique and in PascalCase.

If you modified a generated file and then re-ran ninjadog build, your changes will be overwritten. Use partial classes in separate files to add custom logic safely.

ninjadog command not found

If your shell cannot find the ninjadog command after installation:

# Verify the tool is installed
dotnet tool list -g

# If missing, install it
dotnet tool install -g Ninjadog

# If installed but not on PATH, add the .NET tools directory
export PATH="$PATH:$HOME/.dotnet/tools"

On macOS and Linux, you may need to add $HOME/.dotnet/tools to your shell profile (~/.bashrc, ~/.zshrc, etc.) for the path to persist across sessions.


Next Steps