Skip to content

API Reference

Complete API documentation for GrydCrud module.

Endpoints

GrydCrud automatically generates RESTful endpoints for your entities.

Base URL Pattern

/api/{entity-plural}

Example for Product entity: /api/products


CRUD Operations

Create Entity

Create a new entity.

http
POST /api/{entities}
Content-Type: application/json
Authorization: Bearer {token}

Request Body:

json
{
  "name": "Product Name",
  "description": "Product description",
  "price": 99.99,
  "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

Response:

http
HTTP/1.1 201 Created
Location: /api/products/550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Product Name",
    "description": "Product description",
    "price": 99.99,
    "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "createdAt": "2024-01-15T10:30:00Z",
    "createdBy": "user@example.com"
  }
}

Error Response (400):

json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more validation errors occurred",
    "details": [
      { "field": "name", "message": "Name is required" },
      { "field": "price", "message": "Price must be greater than 0" }
    ]
  }
}

Get Entity by ID

Retrieve a single entity by its unique identifier.

http
GET /api/{entities}/{id}
Authorization: Bearer {token}

Response:

http
HTTP/1.1 200 OK
Content-Type: application/json
json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Product Name",
    "description": "Product description",
    "price": 99.99,
    "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "category": {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "Electronics"
    },
    "createdAt": "2024-01-15T10:30:00Z"
  }
}

Error Response (404):

json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Product with id '550e8400-e29b-41d4-a716-446655440000' not found"
  }
}

Query Entities (Paginated)

Retrieve a paginated list of entities with optional filtering and sorting.

http
GET /api/{entities}?page=1&pageSize=20&search=keyword&sortBy=name&sortOrder=asc
Authorization: Bearer {token}

Query Parameters:

ParameterTypeDefaultDescription
pageint1Page number (1-indexed)
pageSizeint20Items per page (max: 100)
searchstringnullFull-text search term
sortBystring"CreatedAt"Property to sort by
sortDirectionenumDescendingSort direction: Ascending or Descending
includeDeletedboolfalseInclude soft-deleted items

Response:

http
HTTP/1.1 200 OK
Content-Type: application/json
json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "name": "Product A",
        "price": 99.99
      },
      {
        "id": "660e8400-e29b-41d4-a716-446655440001",
        "name": "Product B",
        "price": 149.99
      }
    ],
    "page": 1,
    "pageSize": 20,
    "totalCount": 150,
    "totalPages": 8,
    "hasNextPage": true,
    "hasPreviousPage": false
  }
}

Update Entity

Update an existing entity.

http
PUT /api/{entities}/{id}
Content-Type: application/json
Authorization: Bearer {token}

Request Body:

json
{
  "name": "Updated Product Name",
  "description": "Updated description",
  "price": 129.99
}

Response:

http
HTTP/1.1 200 OK
Content-Type: application/json
json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Updated Product Name",
    "description": "Updated description",
    "price": 129.99,
    "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "createdAt": "2024-01-15T10:30:00Z",
    "updatedAt": "2024-01-15T14:45:00Z",
    "updatedBy": "user@example.com"
  }
}

Delete Entity

Delete an entity (soft delete if entity implements ISoftDeletable).

http
DELETE /api/{entities}/{id}
Authorization: Bearer {token}

Response:

http
HTTP/1.1 204 No Content

Error Response (404):

json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Product with id '550e8400-e29b-41d4-a716-446655440000' not found"
  }
}

Filtering

Simple Filters

http
GET /api/products?categoryId=3fa85f64-5717-4562-b3fc-2c963f66afa6&isActive=true

Range Filters

http
GET /api/products?minPrice=50&maxPrice=200
GET /api/products?createdAfter=2024-01-01&createdBefore=2024-12-31

Multiple Values

http
GET /api/products?status=active,pending,review
GET /api/products?categoryIds=guid1,guid2,guid3

Nested Filters

http
GET /api/products?category.name=Electronics
GET /api/products?supplier.country=USA

Sorting

Single Field

http
GET /api/products?sortBy=price&sortOrder=asc
GET /api/products?sortBy=createdAt&sortOrder=desc

Multiple Fields

http
GET /api/products?sort=category.name:asc,price:desc

Includes (Eager Loading)

Request related entities to be included in the response.

http
GET /api/products/{id}?include=category,images,reviews
GET /api/products?include=category

http
GET /api/products?search=laptop gaming
http
GET /api/products?name.contains=Pro&description.contains=wireless

Batch Operations

Batch Create

http
POST /api/{entities}/batch
Content-Type: application/json
Authorization: Bearer {token}
json
{
  "items": [
    { "name": "Product 1", "price": 99.99 },
    { "name": "Product 2", "price": 149.99 },
    { "name": "Product 3", "price": 199.99 }
  ]
}

Response:

json
{
  "success": true,
  "data": {
    "created": 3,
    "items": [
      { "id": "...", "name": "Product 1" },
      { "id": "...", "name": "Product 2" },
      { "id": "...", "name": "Product 3" }
    ]
  }
}

Batch Update

http
PUT /api/{entities}/batch
Content-Type: application/json
Authorization: Bearer {token}
json
{
  "items": [
    { "id": "guid1", "price": 109.99 },
    { "id": "guid2", "price": 159.99 }
  ]
}

Batch Delete

http
DELETE /api/{entities}/batch
Content-Type: application/json
Authorization: Bearer {token}
json
{
  "ids": ["guid1", "guid2", "guid3"]
}

Export

CSV Export

http
GET /api/{entities}/export?format=csv
Accept: text/csv
Authorization: Bearer {token}

Excel Export

http
GET /api/{entities}/export?format=xlsx
Accept: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Authorization: Bearer {token}

JSON Export

http
GET /api/{entities}/export?format=json
Accept: application/json
Authorization: Bearer {token}

Import

http
POST /api/{entities}/import
Content-Type: multipart/form-data
Authorization: Bearer {token}

Form Data:

  • file: CSV or Excel file
  • options: Import options (optional)

Response:

json
{
  "success": true,
  "data": {
    "imported": 150,
    "skipped": 5,
    "errors": [
      { "row": 23, "message": "Invalid category ID" },
      { "row": 45, "message": "Duplicate SKU" }
    ]
  }
}

Response Formats

Success Response

json
{
  "success": true,
  "data": { /* entity or collection */ },
  "meta": {
    "timestamp": "2024-01-15T10:30:00Z",
    "requestId": "abc-123-def"
  }
}

Error Response

json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human readable message",
    "details": [ /* optional additional details */ ],
    "traceId": "abc-123-def"
  }
}

Error Codes

CodeHTTP StatusDescription
VALIDATION_ERROR400Request validation failed
BAD_REQUEST400Malformed request
UNAUTHORIZED401Authentication required
FORBIDDEN403Insufficient permissions
NOT_FOUND404Entity not found
CONFLICT409Duplicate or conflicting data
UNPROCESSABLE_ENTITY422Business rule violation
RATE_LIMITED429Too many requests
INTERNAL_ERROR500Internal server error

Rate Limits

EndpointLimitWindow
POST /api/{entities}1001 minute
PUT /api/{entities}/{id}1001 minute
DELETE /api/{entities}/{id}501 minute
GET /api/{entities}5001 minute
GET /api/{entities}/{id}10001 minute
POST /api/{entities}/batch101 minute
GET /api/{entities}/export51 minute

Rate Limit Headers:

http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705318200

Service Interfaces

ICrudService

csharp
public interface ICrudService<TEntity, in TCreateDto, in TUpdateDto, TResultDto, in TQueryParameters>
    where TEntity : class, IEntity
    where TQueryParameters : QueryParameters
{
    Task<Result<TResultDto>> CreateAsync(TCreateDto dto, CancellationToken ct = default);
    Task<Result<TResultDto>> UpdateAsync(Guid id, TUpdateDto dto, CancellationToken ct = default);
    Task<Result> DeleteAsync(Guid id, CancellationToken ct = default);
    Task<Result<TResultDto>> GetByIdAsync(Guid id, CancellationToken ct = default);
    Task<Result<PagedResult<TResultDto>>> GetPagedAsync(TQueryParameters parameters, CancellationToken ct = default);
}

// Simplified version with unified DTO
public interface ICrudService<TEntity, TDto>
    : ICrudService<TEntity, TDto, TDto, TDto, QueryParameters>
    where TEntity : class, IEntity
{
}

ICreateOperation

csharp
public interface ICreateOperation<TEntity, in TCreateDto, TResultDto>
    where TEntity : class, IEntity
{
    Task<Result<TResultDto>> ExecuteAsync(TCreateDto dto, CancellationToken cancellationToken = default);
}

IUpdateOperation

csharp
public interface IUpdateOperation<TEntity, in TUpdateDto, TResultDto>
    where TEntity : class, IEntity
{
    Task<Result<TResultDto>> ExecuteAsync(Guid id, TUpdateDto dto, CancellationToken cancellationToken = default);
}

IDeleteOperation

csharp
public interface IDeleteOperation<TEntity>
    where TEntity : class, IEntity
{
    Task<Result> ExecuteAsync(Guid id, CancellationToken cancellationToken = default);
}

IGetByIdOperation

csharp
public interface IGetByIdOperation<TEntity, TResultDto>
    where TEntity : class, IEntity
{
    Task<Result<TResultDto>> ExecuteAsync(Guid id, CancellationToken cancellationToken = default);
}

IQueryOperation

csharp
public interface IQueryOperation<TEntity, TResultDto, in TQueryParameters>
    where TEntity : class, IEntity
    where TQueryParameters : QueryParameters
{
    Task<Result<PagedResult<TResultDto>>> ExecuteAsync(TQueryParameters parameters, CancellationToken cancellationToken = default);
}

Configuration Options

CrudOptions

csharp
public class CrudOptions
{
    /// <summary>
    /// Use soft delete instead of hard delete. Default: true.
    /// </summary>
    public bool UseSoftDelete { get; set; } = true;
    
    /// <summary>
    /// Default page size for queries. Default: 20.
    /// </summary>
    public int DefaultPageSize { get; set; } = 20;
    
    /// <summary>
    /// Maximum page size allowed. Default: 100.
    /// </summary>
    public int MaxPageSize { get; set; } = 100;
    
    /// <summary>
    /// Default sort field when not specified. Default: "CreatedAt".
    /// </summary>
    public string DefaultSortField { get; set; } = "CreatedAt";
    
    /// <summary>
    /// Default sort direction when not specified. Default: Descending.
    /// </summary>
    public SortDirection DefaultSortDirection { get; set; } = SortDirection.Descending;
    
    /// <summary>
    /// Enable automatic validation using registered validators. Default: true.
    /// </summary>
    public bool EnableValidation { get; set; } = true;
    
    /// <summary>
    /// Enable automatic audit tracking (CreatedBy, UpdatedBy). Default: true.
    /// </summary>
    public bool EnableAuditTracking { get; set; } = true;
}

public enum SortDirection
{
    Ascending,
    Descending
}

Examples

Complete Entity Setup

csharp
// Entity - inherits from AggregateRoot which already implements 
// IEntity, ISoftDeletable, and IAuditable
public class Product : AggregateRoot
{
    public string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public Guid CategoryId { get; set; }
    public virtual Category? Category { get; set; }
    
    // ISoftDeletable - inherited: IsDeleted, DeletedAt, DeletedBy, Delete(), Restore()
    // IAuditable - inherited: CreatedAt, UpdatedAt, CreatedBy (Guid?), UpdatedBy (Guid?)
}

// DTOs
public record ProductDto(
    Guid Id,
    string Name,
    string Description,
    decimal Price,
    Guid CategoryId,
    string CategoryName,
    DateTime CreatedAt);

public record CreateProductDto(
    string Name,
    string Description,
    decimal Price,
    Guid CategoryId);

public record UpdateProductDto(
    string Name,
    string Description,
    decimal Price,
    Guid CategoryId);

// Validator
public class CreateProductDtoValidator : AbstractValidator<CreateProductDto>
{
    public CreateProductDtoValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
        RuleFor(x => x.Price).GreaterThan(0);
        RuleFor(x => x.CategoryId).NotEmpty();
    }
}

// Controller - note the correct generic parameters
[ApiController]
[Route("api/products")]
public class ProductsController : CrudController<Product, CreateProductDto, UpdateProductDto, ProductDto, QueryParameters>
{
    public ProductsController(
        ICrudService<Product, CreateProductDto, UpdateProductDto, ProductDto, QueryParameters> service)
        : base(service)
    {
    }
}

Custom Query Parameters

csharp
public class ProductQueryParameters : QueryParameters
{
    public decimal? MinPrice { get; set; }
    public decimal? MaxPrice { get; set; }
    public Guid? CategoryId { get; set; }
    public bool? InStock { get; set; }
}

// Controller
[HttpGet]
public async Task<IActionResult> Query([FromQuery] ProductQueryParameters parameters)
{
    var result = await _service.QueryAsync(parameters);
    return Ok(result);
}

OpenAPI/Swagger

GrydCrud automatically generates OpenAPI documentation for all endpoints.

Accessing Documentation

GET /swagger/index.html
GET /swagger/v1/swagger.json

Customizing Documentation

csharp
builder.Services.AddGrydCrudSwagger(options =>
{
    options.Title = "My API";
    options.Version = "v1";
    options.Description = "API documentation for My Application";
    options.ContactEmail = "api@example.com";
    options.LicenseUrl = "https://example.com/license";
});

Released under the MIT License.