PHPackages                             rakhavirgiandi/laravel-apigator - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. rakhavirgiandi/laravel-apigator

ActiveLibrary[API Development](/categories/api)

rakhavirgiandi/laravel-apigator
===============================

Laravel package to auto-generate CRUD API (Controller, Model, Routes) from database tables

2.0.2(1w ago)04↑650%MITPHPPHP ^8.1

Since May 16Pushed 1w agoCompare

[ Source](https://github.com/rakhavirgiandi/laravel-apigator)[ Packagist](https://packagist.org/packages/rakhavirgiandi/laravel-apigator)[ RSS](/packages/rakhavirgiandi-laravel-apigator/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (8)Versions (9)Used By (0)

🚀 Laravel Apigator
==================

[](#-laravel-apigator)

Auto-generate production-ready CRUD APIs — Controller, Model, and Routes — straight from your database tables. Comes with DataTables support, dynamic query filtering, custom schema joins, and full SQL injection protection.

---

Table of Contents
-----------------

[](#table-of-contents)

1. [Requirements](#requirements)
2. [Installation](#installation)
3. [Quick Start](#quick-start)
4. [Command Options](#command-options)
5. [Generated Files](#generated-files)
6. [API Endpoints](#api-endpoints)
7. [Dynamic Query Filtering](#dynamic-query-filtering)
8. [Eager Loading Relations](#eager-loading-relations)
9. [Custom Schema (mapSchema)](#custom-schema-mapschema)
10. [DataTables Integration](#datatables-integration)
11. [Validation Rules](#validation-rules)
12. [Configuration](#configuration)
13. [Security](#security)
14. [Advanced Usage](#advanced-usage)
15. [File Structure](#file-structure)

---

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1
- Laravel &gt;= 10.x
- MySQL, MariaDB, PostgreSQL, SQLite, or SQL Server

---

Installation
------------

[](#installation)

```
composer require rakhavirgiandi/laravel-apigator
```

Publish the config file (optional):

```
php artisan vendor:publish --tag=apigator-config
```

---

Quick Start
-----------

[](#quick-start)

```
# Generate everything for a single table
php artisan apigator:generate --table=products

# Generate for all tables at once
php artisan apigator:generate --table=all

# Use a specific database connection
php artisan apigator:generate --table=products --connection=mysql_secondary

# Only generate specific parts
php artisan apigator:generate --table=products --generate=model,controller

# Custom output directories
php artisan apigator:generate --table=products \
    --controller-dir=Http/Controllers/API/V1 \
    --model-dir=Models/API

# Overwrite existing files
php artisan apigator:generate --table=products --force
```

---

Command Options
---------------

[](#command-options)

OptionDescriptionDefault`--table=`Table name, or `all` to generate for every table*(required)*`--connection=`Database connection to useApp default connection`--generate=`Comma-separated list: `model`, `controller`, `route`All three`--controller-dir=`Controller directory (relative to `app/`)`Http/Controllers/API``--model-dir=`Model directory (relative to `app/`)`Models``--force`Overwrite existing files`false`### `--connection`

[](#--connection)

Point the generator to any connection defined in `config/database.php`:

```
php artisan apigator:generate --table=orders --connection=pgsql
php artisan apigator:generate --table=all    --connection=mysql_secondary
```

Connection resolution order: `--connection` → `config('apigator.connection')` → app default.

> The generator validates the connection exists before running. An invalid connection name will exit early with a clear error.

### `--generate`

[](#--generate)

Pick exactly what gets created — useful when you only need to regenerate one part:

```
# Model only
php artisan apigator:generate --table=products --generate=model

# Model + controller, skip routes
php artisan apigator:generate --table=products --generate=model,controller

# Routes only (model and controller already exist)
php artisan apigator:generate --table=products --generate=route --force
```

Valid values: `model`, `controller`, `route`. Order doesn't matter — files are always generated in the correct sequence (model → controller → route).

### Safety Checks

[](#safety-checks)

The generator automatically:

- Verifies the table exists in the database before generating anything
- Skips already-generated files (unless `--force` is passed)
- Only checks file existence for the parts you're actually generating
- Skips system tables (`migrations`, `sessions`, `cache`, etc.) when using `--table=all`

---

Generated Files
---------------

[](#generated-files)

For a table named `products`, the command produces:

```
app/
  Models/
    Product.php                 ← Eloquent model with ApiModelTrait
  Http/Controllers/API/
    ProductController.php       ← Thin controller, logic lives in the model
routes/
  api.php                       ← 6 routes appended inside the Apigator marker block

```

### Route Marker Block

[](#route-marker-block)

All generated routes are written inside a clearly marked area in your route file. This makes them easy to find and prevents duplication:

```
// [APIGATOR_ENDPOINTS_START]

// [APIGATOR_ENDPOINTS] products
Route::get('/products',            [ProductController::class, 'index']);
Route::get('/products/{id}',       [ProductController::class, 'show']);
// ...

// [APIGATOR_ENDPOINTS] orders
Route::get('/orders',              [OrderController::class, 'index']);
// ...

// [APIGATOR_ENDPOINTS_END]
```

If the marker block doesn't exist yet, it is created at the end of the file automatically. New tables are always inserted just before `[APIGATOR_ENDPOINTS_END]`.

---

API Endpoints
-------------

[](#api-endpoints)

For a table `products` (model `Product`, slug `products`):

MethodURLDescription`GET``/products`Paginated list`GET``/products/{id}`Single record by ID`POST``/products`Create a record`PATCH``/products/{id}`Partial update`DELETE``/products/{id}`Delete a record`POST``/products_datatable`DataTables server-side endpoint### GET /products — List

[](#get-products--list)

```
GET /products?page=1&per_page=15

```

```
{
    "success": true,
    "message": "Success",
    "data": {
        "meta": {
            "current_page": 1,
            "per_page": 15,
            "total_pages": 4,
            "total_items": 48
        },
        "data": [...]
    }
}
```

### GET /products/{id} — Single Record

[](#get-productsid--single-record)

```
GET /products/5

```

Search by a custom column (must be a real column):

```
GET /products/ABC-001?column=code

```

### POST /products — Create

[](#post-products--create)

```
{
    "name": "Widget A",
    "price": 29.99,
    "category_id": 3
}
```

Response `201`:

```
{
    "success": true,
    "message": "Product created successfully.",
    "data": { "id": 42, "name": "Widget A", ... }
}
```

### PATCH /products/{id} — Partial Update

[](#patch-productsid--partial-update)

Only send the fields you want to change:

```
{ "price": 34.99 }
```

### DELETE /products/{id} — Delete

[](#delete-productsid--delete)

If the model uses `SoftDeletes`, the record is soft-deleted instead of permanently removed.

---

Dynamic Query Filtering
-----------------------

[](#dynamic-query-filtering)

All `GET` list endpoints and the DataTables endpoint support rich query filtering via URL parameters. Every filter is **SQL-injection safe** — column names are whitelisted against the actual schema and sanitized before use.

### Basic Equality

[](#basic-equality)

```
GET /products?status=active
GET /products?category_id=3

```

### Operators

[](#operators)

Append `[operator]` to any column name:

ParameterSQL Equivalent`?col[eq]=val``col = val``?col[neq]=val``col != val``?col[gt]=val``col > val``?col[gte]=val``col >= val``?col[lt]=val``col < val``?col[lte]=val``col = 2024-01-01``?col[date_to]=2024-12-31``DATE(col)  'user.organization,role',
]);
```

### Supported Formats

[](#supported-formats)

FormatExampleSingle relation`?with=user`Multiple relations`?with=user,role`Nested (dot notation)`?with=user.organization`Multiple nested`?with=user.organization,role.permissions`---

Custom Schema (mapSchema)
-------------------------

[](#custom-schema-mapschema)

The generated model includes a `mapSchema()` method where you define custom SELECT columns, JOIN definitions, and static WHERE conditions.

### Example: Products with Category join and inventory calculation

[](#example-products-with-category-join-and-inventory-calculation)

```
public static function mapSchema(array $params = [], array $user = []): array
{
    $model = new self;
    $warehouseId = $params['warehouse_id'] ?? '';

    return [
        'field' => [
            'id'            => ['column' => $model->table.'.id',         'alias' => 'id',           'type' => 'int'],
            'code'          => ['column' => $model->table.'.code',        'alias' => 'code',          'type' => 'string'],
            'name'          => ['column' => $model->table.'.name',        'alias' => 'name',          'type' => 'string'],
            'category_id'   => ['column' => $model->table.'.category_id', 'alias' => 'category_id',   'type' => 'int'],
            'category_name' => ['column' => 'cat.name',                   'alias' => 'category_name', 'type' => 'string'],
            'qty_on_hand'   => [
                'column' => 'COALESCE(inv.qty, 0)',
                'alias'  => 'qty_on_hand',
                'type'   => 'float',
                'is_raw' => true,   // ← treated as a raw SQL expression
            ],
            'has_variants'  => [
                'column' => "CASE WHEN EXISTS (SELECT 1 FROM product_variants pv WHERE pv.product_id = {$model->table}.id) THEN 1 ELSE 0 END",
                'alias'  => 'has_variants',
                'type'   => 'bool',
                'is_raw' => true,
            ],
        ],
        'join' => [
            [
                'table' => 'categories as cat',
                'type'  => 'left',
                'on'    => ['cat.id', '=', $model->table.'.category_id'],
            ],
            [
                'table' => DB::raw("
                    (
                        SELECT product_id, SUM(
                            CASE WHEN type = 'IN' THEN qty ELSE -qty END
                        ) AS qty
                        FROM inventory_movements
                        WHERE deleted_at IS NULL
                        " . ($warehouseId ? "AND warehouse_id = {$warehouseId}" : "") . "
                        GROUP BY product_id
                    ) as inv
                "),
                'type'  => 'left',
                'on'    => ['inv.product_id', '=', $model->table.'.id'],
            ],
        ],
        'where' => [
            ['column' => $model->table.'.deleted_at', 'operator' => 'IS NULL', 'value' => null],
        ],
    ];
}
```

### Field Definition Reference

[](#field-definition-reference)

KeyTypeDescription`column``string`SQL column expression — `table.column` or a raw SQL expression`alias``string`The key name returned in the response`type``string``string`, `int`, `float`, `bool`, `date`, `datetime`, `json``is_raw``bool`When `true`, the `column` value is used as raw SQL (not quoted)### Dynamic Parameters in mapSchema

[](#dynamic-parameters-in-mapschema)

`mapSchema` receives the full request `$params` array, so you can drive query logic from any request parameter:

```
// ?warehouse_id=5 filters inventory per warehouse
$warehouseId = $params['warehouse_id'] ?? '';
```

---

DataTables Integration
----------------------

[](#datatables-integration)

### JavaScript Setup (DataTables 1.x / 2.x)

[](#javascript-setup-datatables-1x--2x)

```
$('#table').DataTable({
    processing: true,
    serverSide: true,
    ajax: {
        url: '/api/products_datatable',
        type: 'POST',
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'),
            'Authorization': 'Bearer ' + token,
        },
    },
    columns: [
        { data: 'id',     name: 'id' },
        { data: 'name',   name: 'name' },
        { data: 'price',  name: 'price' },
        { data: 'status', name: 'status', searchable: false },
    ],
});
```

### Server Response Format

[](#server-response-format)

```
{
    "draw": 1,
    "recordsTotal": 500,
    "recordsFiltered": 48,
    "data": [...]
}
```

### Features

[](#features)

- Global search (across all `searchable: true` columns)
- Per-column search and multi-column sort
- Pagination via `start` / `length`
- Works with `mapSchema` joins and computed columns
- Compatible with DataTables 1.x and 2.x

---

Validation Rules
----------------

[](#validation-rules)

Validation rules are **auto-generated from the database schema** at generation time. They live in two static methods on the model:

```
Product::createRules()   // Used by POST  — required fields are marked required
Product::updateRules()   // Used by PATCH — all fields become optional
```

### Type → Rule Mapping

[](#type--rule-mapping)

DB TypeValidation Rule`int`, `int2`, `int4`, `int8`, `integer`, `bigint`, `smallint``integer``decimal`, `numeric`, `float`, `double``numeric``bool`, `boolean``boolean``date``date``datetime`, `timestamp``date``time``date_format:H:i:s``json`, `jsonb``json`Everything else`string`Non-nullable columns → `required`. Nullable columns → `nullable`.

You can customize the rules directly in the model at any time:

```
public static function createRules(): array
{
    return [
        'name'   => ['required', 'string', 'max:255', 'unique:products,name'],
        'email'  => ['required', 'email'],
        'price'  => ['required', 'numeric', 'min:0'],
        'status' => ['required', 'string', 'in:active,inactive,draft'],
        'image'  => ['nullable', 'image', 'max:2048'],
    ];
}
```

---

Configuration
-------------

[](#configuration)

`config/apigator.php`:

```
return [
    // Default database connection (falls back to app default if not set)
    'connection' => null,

    // Default controller directory (relative to app/)
    'controller_directory' => 'Http/Controllers/API',

    // Default model directory (relative to app/)
    'model_directory' => 'Models',

    // Default API route delimiter
    'route_delimiter' => '_',

    // Route file where generated routes are appended
    'route_file' => 'routes/api.php',

    // Default items per page
    'default_per_page' => 10,

    // Tables skipped when using --table=all
    'exclude_tables' => [
        'migrations', 'password_resets', 'failed_jobs',
        'personal_access_tokens', 'sessions', 'cache',
        'cache_locks', 'jobs', 'job_batches',
    ],
];
```

> **Note:** `route_middleware` has been removed. Apply middleware directly in your route file using Laravel's standard `Route::middleware(...)` wrapper around the Apigator marker block if needed.

---

Security
--------

[](#security)

### SQL Injection Protection

[](#sql-injection-protection)

Every dynamic parameter passes through a multi-layer defense:

1. **Column whitelist** — column names are validated against `Schema::getColumnListing()`. Unknown columns are silently ignored.
2. **Operator whitelist** — only operators in the `OPERATORS` constant are accepted. Unknown operators are silently ignored.
3. **Column sanitization** — after whitelist check, column names are regex-sanitized to `[a-zA-Z0-9_.]` only.
4. **LIKE escaping** — `%` and `_` in user values are escaped before being used in `LIKE` clauses.
5. **Parameter binding** — all values go through PDO parameter binding; nothing is ever interpolated directly.

### Input Validation

[](#input-validation)

- All `POST` and `PATCH` data is validated through Laravel's `Validator` before touching the database.
- The `column` parameter in `GET /{slug}/{id}?column=X` is validated against `Schema::getColumnListing()`.

---

Advanced Usage
--------------

[](#advanced-usage)

### Using ApiModelTrait in Existing Models

[](#using-apimodeltrait-in-existing-models)

Add the trait to any existing model without regenerating:

```
use Virgiandi\ApiGenerator\Traits\ApiModelTrait;

class Product extends Model
{
    use ApiModelTrait;

    public static function mapSchema(array $params = [], array $user = []): array
    {
        // your schema definition
    }
}
```

### Calling Model Methods Directly

[](#calling-model-methods-directly)

```
// Paginated list with filters
$result = Product::getList([
    'status'     => 'active',
    'price[lte]' => 100,
    '_sort'      => '-created_at',
    'per_page'   => 20,
    'with'       => 'category,user.organization',  // eager load relations
]);

// Single record
$product = Product::getById(5);
$product = Product::getById('PROD-001', ['column' => 'sku']);

// Create / update / delete
$product = Product::createRecord(['name' => 'New', 'price' => 9.99]);
Product::updateRecord(5, ['price' => 14.99]);
Product::deleteRecord(5);

// DataTables
$data = Product::getDatatable($request->all());
```

### Extending the Controller

[](#extending-the-controller)

Generated controllers are intentionally thin. Add custom logic by extending:

```
class ProductController extends \App\Http\Controllers\API\ProductController
{
    public function store(Request $request): JsonResponse
    {
        $request->merge(['created_by' => auth()->id()]);
        return parent::store($request);
    }
}
```

---

File Structure
--------------

[](#file-structure)

```
laravel-apigator/
├── composer.json
├── config/
│   └── apigator.php
└── src/
    ├── ApigatorServiceProvider.php
    ├── Commands/
    │   └── GenerateApiCommand.php        ← Artisan command
    ├── Generators/
    │   ├── ModelGenerator.php            ← Builds the Model PHP file
    │   ├── ControllerGenerator.php       ← Builds the Controller PHP file
    │   └── RouteGenerator.php            ← Appends routes inside the marker block
    ├── Support/
    │   ├── DynamicQueryParser.php        ← Parses URL params into Eloquent filters
    │   ├── SchemaQueryBuilder.php        ← Builds queries from mapSchema definitions
    │   └── ValidationRuleBuilder.php     ← Derives validation rules from column types
    └── Traits/
        ├── ApiModelTrait.php             ← Core CRUD + DataTables logic (on Model)
        └── ApiControllerTrait.php        ← JSON response helpers (on Controller)

```

---

License
-------

[](#license)

MIT

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance98

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

7

Last Release

8d ago

Major Versions

1.9.5 → 2.0.02026-06-01

### Community

Maintainers

![](https://www.gravatar.com/avatar/cb18373e966bf7e9bfaa96c569b515ae2abba15cf66cc951f3507dcd3f4b3da8?d=identicon)[rakhavirgiandi](/maintainers/rakhavirgiandi)

### Embed Badge

![Health badge](/badges/rakhavirgiandi-laravel-apigator/health.svg)

```
[![Health](https://phpackages.com/badges/rakhavirgiandi-laravel-apigator/health.svg)](https://phpackages.com/packages/rakhavirgiandi-laravel-apigator)
```

###  Alternatives

[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[illuminate/queue

The Illuminate Queue package.

20432.2M1.5k](/packages/illuminate-queue)[spatie/laravel-health

Monitor the health of a Laravel application

88011.3M149](/packages/spatie-laravel-health)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45344.0k1](/packages/pressbooks-pressbooks)[erag/laravel-lang-sync-inertia

A powerful Laravel package for syncing and managing language translations across backend and Inertia.js (Vue/React) frontends, offering effortless localization, auto-sync features, and smooth multi-language support for modern Laravel applications.

4721.5k](/packages/erag-laravel-lang-sync-inertia)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
