PHPackages                             zirvu/api - 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. zirvu/api

ActiveLibrary[API Development](/categories/api)

zirvu/api
=========

Zirvu API

2.0.0(1mo ago)053PHPPHP ^8.2

Since Dec 6Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/zirvu/api)[ Packagist](https://packagist.org/packages/zirvu/api)[ RSS](/packages/zirvu-api/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (3)Versions (20)Used By (0)

🚀 Zirvu API Package
===================

[](#-zirvu-api-package)

A Laravel package that provides a modular service-repository architecture with built-in CRUD traits, dynamic service loading, and a scaffolding CLI command — so you can spin up a full backend module in one command.

```
composer require zirvu/api
```

---

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

[](#-table-of-contents)

- [Installation](#installation)
- [Scaffolding — `zirvu:add_module`](#scaffolding)
- [Route Auto-Discovery](#route-auto-discovery)
- [Architecture Overview](#architecture-overview)
- [Traits Reference](#traits-reference)
    - [API (Controller)](#api-trait)
    - [Service](#service-trait)
    - [Repository](#repository-trait)
    - [Relation (Model)](#relation-trait)
- [Service Methods](#service-methods)
- [Controller Methods](#controller-methods)
- [Customization](#customization)
- [Dynamic Service Loading](#dynamic-service-loading)
- [Configuration](#configuration)
- [Suggested Project Structure](#suggested-project-structure)

---

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

[](#installation)

### 1. Require the package

[](#1-require-the-package)

```
composer require zirvu/api
```

### 2. Publish config

[](#2-publish-config)

```
php artisan vendor:publish --provider="Zirvu\Api\ZirvuApiPackageServiceProvider" --tag=config
```

Publishes `config/zirvu/api/classes.php` — the registry of all your module class names.

### 3. Publish `RepositoryServiceProvider`

[](#3-publish-repositoryserviceprovider)

```
php artisan vendor:publish --provider="Zirvu\Api\ZirvuApiPackageServiceProvider" --tag=providers
```

Publishes `app/Providers/RepositoryServiceProvider.php` — a **dynamic** provider that automatically reads `classes.php` and binds every `*Contract → *Repository` and `*Contract → *Service` pair. No manual `use`/`bind` per class needed.

### 4. Register the provider

[](#4-register-the-provider)

How you register depends on your **Laravel version**:

#### Laravel 11+ — `bootstrap/providers.php`

[](#laravel-11--bootstrapprovidersphp)

```
// bootstrap/providers.php
return [
    App\Providers\AppServiceProvider::class,
    App\Providers\RepositoryServiceProvider::class,  // ← add this
];
```

> **Auto-registered:** `zirvu:add_module` detects Laravel 11+ and adds this entry automatically on first run.

#### Laravel 10 and below — `config/app.php`

[](#laravel-10-and-below--configappphp)

```
// config/app.php
'providers' => [
    // ...
    App\Providers\RepositoryServiceProvider::class,  // ← add this
],
```

> For Laravel 10 and below, the command will print a reminder with the line to copy — it cannot safely auto-edit `config/app.php`.

### 5. (Optional) `AppServiceProvider` — auto-observer

[](#5-optional-appserviceprovider--auto-observer)

If you use Auditing observers, add this to your `AppServiceProvider::boot()` to auto-register an observer for every model in `classes.php`:

```
public function boot(): void
{
    $this->observerModels();
}

public function observerModels(): void
{
    $configs = config('zirvu.api.classes');

    foreach ($configs as $key => $value) {
        foreach ($value as $class) {
            $model = ($key !== 'root')
                ? "App\\Models\\{$key}\\{$class}"
                : "App\\Models\\{$class}";

            if (class_exists($model)) {
                $model::observe(\App\Observers\Observer::class);
            }
        }
    }
}
```

---

Scaffolding
-----------

[](#scaffolding)

The package ships with a scaffolding command that generates a full backend module in one shot.

### Basic Usage

[](#basic-usage)

```
php artisan zirvu:add_module --name=ProductDetail
```

### With a Sub-folder Group

[](#with-a-sub-folder-group)

```
php artisan zirvu:add_module --name=ProductDetail --group=MasterData
```

### Multi-word Name (comma separator)

[](#multi-word-name-comma-separator)

Use this if the auto-detection of word boundaries doesn't produce the result you want:

```
php artisan zirvu:add_module --name=Product,Detail
```

All three examples above produce the same result. The command will show you a confirmation table before creating anything:

```
  Module:         ProductDetail
  Group:
  prefix:         productdetail
  prefix_script:  productDetail
  table_name:     product_details
  migration:      2026_05_24_091902_create_product_details_table.php

  Create module with the above settings? (yes/no) [yes]

```

### Naming Convention

[](#naming-convention)

Input`class_name``prefix``prefix_script``table_name``--name=ProductDetail``ProductDetail``productdetail``productDetail``product_details``--name=Product``Product``product``product``products``--name=Product,Detail``ProductDetail``productdetail``productDetail``product_details`### Files Generated

[](#files-generated)

Running `--name=ProductDetail` (no group) creates **9 files**:

FileDescription`routes/productdetail/api.php`JWT-protected route group`app/Http/Controllers/Api/ProductDetailController.php`API controller`app/Models/ProductDetail.php`Eloquent model`app/Services/Contract/ProductDetailContract.php`Service interface`app/Services/ProductDetailService.php`Business logic`app/Repositories/Contract/ProductDetailContract.php`Repository interface`app/Repositories/ProductDetailRepository.php`Data access layer`database/migrations/{timestamp}_create_product_details_table.php`Migration`postman/ProductDetail.json`Postman collection (v2.1)With `--group=MasterData`, all files go into a `MasterData/` sub-folder and namespace. The route folder and prefix are also grouped:

- Route folder: `routes/MasterData/productdetail/api.php`
- Route prefix: `masterdata/productdetail`
- Postman file: `postman/MasterData/ProductDetail.json`

> **Note:** Migration files are always created flat in `database/migrations/` regardless of group.

### After Scaffolding

[](#after-scaffolding)

`config/zirvu/api/classes.php` is **updated automatically** — the class is appended to the correct key (`root` or your group name). No manual step needed.

Then just run the migration:

```
php artisan migrate
```

---

Route Auto-Discovery
--------------------

[](#route-auto-discovery)

The scaffolded route files are **automatically picked up** — no manual `require` needed — as long as your `routes/api.php` uses the following auto-discovery pattern:

```
// routes/api.php

function getAllPath($path = "routes")
{
    $paths = [];
    $directories = File::directories(base_path($path));

    foreach ($directories as $dir) {
        $subPath = $path . '/' . basename($dir);
        $paths[] = $subPath;
        $paths = array_merge($paths, getAllPath($subPath));
    }

    return $paths;
}

$paths = getAllPath();
foreach ($paths as $key => $path) {
    $apiFilePath = base_path() . "/" . $path . "/api.php";
    if (File::exists($apiFilePath)) {
        require $apiFilePath;
    }
}
```

This recursively walks every sub-folder under `routes/` and includes any `api.php` it finds. So when `zirvu:add_module` creates:

```
routes/productdetail/api.php
routes/MasterData/productdetail/api.php

```

…both are loaded automatically at boot — **no changes to `routes/api.php` required**.

Each generated route file registers these endpoints:

MethodEndpointAction`GET``/{prefix}/data`Paginated list`GET``/{prefix}/one/{id}`Single record`GET``/{prefix}/first`First matching record`POST``/{prefix}/save`Create or update`POST``/{prefix}/savemany`Bulk create/update`DELETE``/{prefix}/delete`Delete records---

Architecture Overview
---------------------

[](#architecture-overview)

```
Request → Controller (API trait)
             ↓
          Service (Service trait)  ← validation, business logic
             ↓
          Repository (Repository trait)  ← DB queries
             ↓
          Model (Relation trait)  ← Eloquent + safe-delete check

```

Each layer communicates through its **Contract (interface)**, bound via Laravel's service container.

---

Traits Reference
----------------

[](#traits-reference)

### API Trait

[](#api-trait)

Used in API controllers. Bundles `CommonController` which provides all standard CRUD endpoint methods automatically.

```
use Zirvu\Api\Traits\API;

class ProductDetailController
{
    use API;

    protected $baseService;

    public function __construct(ProductDetailContract $baseService)
    {
        $this->baseService = $baseService;
    }
}
```

> The `API` trait gives you `data`, `one`, `first`, `save`, `update`, `delete`, and `saveMany` endpoints for free — no extra code needed in the controller.

---

### Service Trait

[](#service-trait)

Used in service classes. Bundles `CommonService` (CRUD logic) and `LoaderService` (dynamic service loading).

```
use Zirvu\Api\Traits\Service;

class ProductDetailService implements ProductDetailContract
{
    use Service;

    public $table_name = "product_details";
    public $type       = "api";
    public $extra      = [];

    public function __construct(ProductDetailRepositoryContract $baseRepository)
    {
        $this->baseRepository = $baseRepository;
    }

    public function buildValidation($fields)
    {
        $this->rule_edit["id"] = [
            "required",
            Rule::exists($this->table_name, "id")->whereNull("deleted_at")
        ];

        $this->rules = [
            "rule_new"    => $this->rule_new,
            "rule_edit"   => $this->rule_edit,
            "rule_public" => $this->rule_public,
        ];
    }
}
```

---

### Repository Trait

[](#repository-trait)

Used in repository classes. Bundles `CommonRepository` (CRUD operations) and `Eloquent` (query building, filtering, pagination).

```
use Zirvu\Api\Traits\Repository;
use App\Models\ProductDetail;

class ProductDetailRepository implements ProductDetailContract
{
    use Repository;

    protected $model;

    public $table_name   = "product_details";
    public $class_name   = "App\Models\ProductDetail";
    public $table_detail;
    public $ignore_save  = [];
    public $with         = [];

    public function __construct(ProductDetail $model)
    {
        $this->model        = $model;
        $this->table_detail = $this->getColumnsFromTable();
    }
}
```

Key properties:

PropertyDescription`$table_name`Used for column introspection and pagination`$class_name`Fully-qualified model class name`$table_detail`Auto-populated map of DB columns → type/nullable/default`$ignore_save`Array of field names to skip when auto-mapping on save`$with`Default eager-loaded relations on `find()` and `first()`---

### Relation Trait

[](#relation-trait)

Used in Eloquent models. Provides a `checkDelete()` method that inspects loaded relations before allowing deletion.

```
use Zirvu\Api\Traits\Relation;

class ProductDetail extends Model implements Auditable
{
    use HasFactory, SoftDeletes, \OwenIt\Auditing\Auditable, Relation;
}
```

The repository's `delete()` calls `$model->checkDelete()` automatically — if any loaded relation has children, deletion is blocked and returns `false`.

---

Service Methods
---------------

[](#service-methods)

All methods are available through the `Service` trait (`CommonService`):

MethodDescription`get(object $options)`Paginated list with filtering and ordering`find($id)`Find a single record by primary key`first(object $options)`First record matching filters`save($id, array $fields)`Create (id=null) or update (id=N) with validation`update($id, array $fields)`Partial update — merges with existing data then calls `save()``delete(array $ids)`Soft-delete one or more records in a transaction`saveMany(array $datas)`Bulk create/update in a single transaction### `get()` — Filter &amp; Pagination Options

[](#get--filter--pagination-options)

```
$data = $this->baseService->get((object)[
    "page"         => 1,
    "take"         => 15,
    "filter"       => "name:contains:product",
    "order"        => "created_at",
    "order_method" => "DESC",
    "with"         => ["category"],   // eager load relations
    "select"       => ["id", "name"], // column selection
]);
```

Supported filter operators: `=`, `!=`, `>`, `=`, `baseService->save(null, [
    "name"  => "New Product",
    "price" => 9900,
]);

// Update
$record = $this->baseService->save(42, [
    "id"    => 42,
    "name"  => "Updated Name",
]);
```

### `saveMany()` — Bulk Save

[](#savemany--bulk-save)

```
$results = $this->baseService->saveMany([
    ["name" => "Item A", "price" => 100],
    ["id" => 5, "name" => "Item B updated"],
]);
```

---

Controller Methods
------------------

[](#controller-methods)

All methods are provided automatically by the `API` trait (`CommonController`). Your controller needs zero additional methods for standard CRUD:

HTTPRouteMethodDescription`GET``/data``data()`Paginated list`GET``/one/{id}``one()`Single record`GET``/first``first()`First matching record`POST``/save``save()`Create or update`POST``/update``update()`Partial update`DELETE``/delete``delete()`Delete records (pass `data: [ids]`)`POST``/save-many``saveMany()`Bulk create/update### Standard JSON Response Format

[](#standard-json-response-format)

```
{
    "success": true,
    "message": "Success!",
    "data": { ... }
}
```

With pagination (from `data()` endpoint):

```
{
    "success": true,
    "message": "Success!",
    "pagination": {
        "take": 10,
        "page": 1,
        "totalPage": 5,
        "total": 48
    },
    "data": [ ... ]
}
```

You can also call `$this->response($statusCode, $withPaginate)` manually if you override a method:

```
public function data(Request $request)
{
    $this->data = $this->baseService->get((object)$request->all());
    $this->pagination = $this->baseService->extra["pagination"] ?? [];
    return $this->response(200, true);
}
```

---

Customization
-------------

[](#customization)

All CRUD logic lives in the traits, but every method can be **freely overridden** by simply declaring the same method in your own class. PHP will always use your version instead of the trait's.

The source of truth for all default implementations is the package itself — browse or copy any method from:

- `Zirvu\Api\Traits\ControllerExtension\CommonController` — controller methods
- `Zirvu\Api\Traits\ServiceExtension\CommonService` — service methods
- `Zirvu\Api\Traits\RepositoryExtension\CommonRepository` — repository methods
- `Zirvu\Api\Traits\RepositoryExtension\Eloquent` — query building methods

### Example — Custom Service `save()`

[](#example--custom-service-save)

Say you want to add business logic before saving (e.g. hash a password, fire an event, call an external API). Copy the default `save()` from the trait and put it in your Service class:

```
// app/Services/ProductDetailService.php

use Zirvu\Api\Traits\Service;
use Illuminate\Validation\Rule;
use \Exception;
use \DB;

class ProductDetailService implements ProductDetailContract
{
    use Service;

    // ... constructor, buildValidation, etc.

    /**
     * Override the trait's save() to add custom business logic.
     */
    public function save($id, array $fields)
    {
        $this->loadUtilsService();
        $this->buildValidation($fields);
        $validator = $this->utilsService->validation($this->rules, $fields);

        if ($validator->fails()) {
            $this->message = $validator->errors()->first();
            $this->data    = $validator->errors();
            throw new Exception($this->message, 1);
        }

        DB::beginTransaction();

        try {
            // ✏️  Your custom logic here:
            $fields['slug'] = str()->slug($fields['name'] ?? '');

            $data = $this->baseRepository->save($id, $fields);

            // ✏️  Or after save:
            // event(new ProductDetailSaved($data));

            DB::commit();
        } catch (Exception $e) {
            throw new Exception($e->getMessage(), 1);
        }

        return $data;
    }
}
```

### Example — Custom Controller endpoint

[](#example--custom-controller-endpoint)

Need extra logic on the `data()` endpoint? Override it in your controller:

```
// app/Http/Controllers/Api/ProductDetailController.php

use Zirvu\Api\Traits\API;

class ProductDetailController
{
    use API;

    protected $baseService;

    public function __construct(ProductDetailContract $baseService)
    {
        $this->baseService = $baseService;
    }

    /**
     * Override data() to inject extra fields into the response.
     */
    public function data(Request $request)
    {
        try {
            $this->data       = $this->baseService->get((object)$request->all());
            $this->pagination = $this->baseService->extra["pagination"] ?? [];

            // ✏️  Append anything extra:
            $this->data = $this->data->map(fn($item) => [
                ...$item->toArray(),
                'display_name' => strtoupper($item->name),
            ]);

        } catch (Exception $e) {
            $this->success = false;
            $this->message = $e->getMessage();
        }

        return $this->response(200, true);
    }
}
```

### Example — Custom Repository query

[](#example--custom-repository-query)

Need to filter by a scope or join a table? Override `getData()` or any query method:

```
// app/Repositories/ProductDetailRepository.php

use Zirvu\Api\Traits\Repository;

class ProductDetailRepository implements ProductDetailContract
{
    use Repository;

    // ... constructor, properties

    /**
     * Override get() to always filter by active status.
     */
    public function get(object $object, string $type)
    {
        $this->model = $this->model->where('is_active', 1);
        return parent::get($object, $type);
        // or call $this->getData($type) directly after your custom scoping
    }
}
```

> **Rule of thumb:** If a method exists in the trait and you declare the same method in your class, your version wins. No extra configuration needed.

---

Dynamic Service Loading
-----------------------

[](#dynamic-service-loading)

The `LoaderService` trait (included via `Service`) lets any service or controller load another registered service on the fly using magic methods:

```
// Loads App\Services\Contract\UserContract via the container
$this->loadUserService();
$users = $this->userService->get((object)[]);

// Loads App\Services\Contract\MasterData\ProductContract
$this->loadProductService();
```

The method name pattern is `load{ClassName}Service()` — it resolves the class from `config/zirvu/api/classes.php` and instantiates it via the service container.

> The class **must** be registered in `config/zirvu/api/classes.php` for this to work.

---

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

[](#configuration)

`config/zirvu/api/classes.php` — registers all module class names for dynamic loading and container binding:

```
return [
    "root" => [
        "Example",
        "Role",
        "User",
        "ProductDetail",  // ← your new module
    ],
    "MasterData" => [
        "Jabatan",
        "Product",        // ← your group module
    ],
];
```

- `"root"` → classes live directly in `App\Services\`, `App\Repositories\`, etc.
- Any other key (e.g. `"MasterData"`) → classes live in the matching sub-folder namespace.

---

Suggested Project Structure
---------------------------

[](#suggested-project-structure)

```
app/
├── Http/
│   └── Controllers/
│       └── Api/
│           ├── ProductDetailController.php   ← use API trait
│           └── MasterData/
│               └── ProductController.php
├── Models/
│   ├── ProductDetail.php                     ← use Relation trait
│   └── MasterData/
│       └── Product.php
├── Services/
│   ├── Contract/
│   │   ├── ProductDetailContract.php
│   │   └── MasterData/
│   │       └── ProductContract.php
│   ├── ProductDetailService.php              ← use Service trait
│   └── MasterData/
│       └── ProductService.php
└── Repositories/
    ├── Contract/
    │   ├── ProductDetailContract.php
    │   └── MasterData/
    │       └── ProductContract.php
├── Repositories/
│   ├── Contract/
│   │   ├── ProductDetailContract.php
│   │   └── MasterData/
│   │       └── ProductContract.php
│   ├── ProductDetailRepository.php           ← use Repository trait
│   └── MasterData/
│       └── ProductRepository.php

routes/
├── productdetail/
│   └── api.php                               ← auto-discovered (save, savemany, etc.)
└── MasterData/
    └── productdetail/
        └── api.php                           ← auto-discovered (group)

postman/
├── ProductDetail.json                        ← Postman v2.1 collection
└── MasterData/
    └── ProductDetail.json                    ← group collection

database/
└── migrations/
    └── 2026_05_24_000000_create_product_details_table.php

```

---

> Made by Zirvu

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance91

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity64

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

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 ~50 days

Recently: every ~145 days

Total

19

Last Release

41d ago

Major Versions

0.0.15 → 1.0.02025-06-28

1.0.2 → 2.0.02026-05-24

PHP version history (2 changes)0.0.0PHP ^7.3|^8.0

0.0.5PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/1bac97023e47b033528b1560d85068e848bb12e25127cc6d20061ceead0a226c?d=identicon)[zirvu](/maintainers/zirvu)

---

Top Contributors

[![zirvu](https://avatars.githubusercontent.com/u/93853818?v=4)](https://github.com/zirvu "zirvu (21 commits)")

---

Tags

laravel-api-service-modular-reusable-scalable

### Embed Badge

![Health badge](/badges/zirvu-api/health.svg)

```
[![Health](https://phpackages.com/badges/zirvu-api/health.svg)](https://phpackages.com/packages/zirvu-api)
```

###  Alternatives

[defstudio/telegraph

A laravel facade to interact with Telegram Bots

816333.6k3](/packages/defstudio-telegraph)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5021.9k](/packages/simplestats-io-laravel-client)[rapidez/core

Rapidez Core

1823.5k72](/packages/rapidez-core)

PHPackages © 2026

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