Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Hello Api Laravel Package

porto-sap/hello-api

Porto SAP Hello API is a Laravel-based starter framework for building scalable, modular APIs. It offers a clean architecture, built-in authentication and authorization, API documentation, validation, and code generators to accelerate enterprise API development.

View on GitHub
Deep Wiki
Context7

Getting Started

Apiato (porto-sap/hello-api) is not a library you composer require into an existing app — it is an opinionated Laravel application skeleton built on the Porto architecture. Adoption means starting from the skeleton (or forking it) and living inside its conventions.

Minimal path to a working API:

composer create-project apiato/apiato my-api   # or clone porto-sap/hello-api
cp .env.example .env
php artisan key:generate
# set DB_*, then:
php artisan migrate            # Apiato wraps/extends migrate; picks up every container's Data/Migrations
php artisan db:seed
php artisan serve

Then open /docs — Apiato generates a live API Blueprint from controller annotations, so the docs page is effectively a map of every endpoint you have. As of v13.2.0 that docs page ships with a modern, responsive UI, so it is now genuinely usable from a phone or tablet — handy for skimming your endpoint surface during a code review or while away from the desk.

Look here first, in this order:

  1. app/Ship/Parents/ — the base classes everything extends: Action, Task, Repository, Transformer, ApiController, ApiRequest, Policy, Middleware. Read Action and Repository before writing anything.
  2. One real container end-to-end — app/Containers/AppSection/User (or Authentication). The folder is the documentation: Actions/, Tasks/, Data/{Models,Repositories,Migrations,Factories,Seeders,Criterias}, UI/API/{Controllers,Requests,Routes,Transformers}, Providers/.
  3. app/Ship/Configs/apiato.php and config/repository.php — request-criteria behaviour, pagination caps, hash-ID toggling.
  4. php artisan list apiato — the generator commands (apiato:generate:container, generate:action, generate:task, generate:request, generate:transformer, generate:controller, generate:repository, …). Scaffold rather than hand-roll; the generators wire providers and route files correctly.

First real use case: add an endpoint to an existing container rather than creating a new one — a new method on User/UI/API/Controllers/Controller.php, a route line in UI/API/Routes/v1.php, a Request, an Action, and reuse of the existing UserTransformer. That teaches you the whole flow in one pass.

Implementation Patterns

The request pipeline is fixed. Route → ApiRequest (validation) → thin controller → Action (one use case) → Task (reusable step) → Repository → Transformer. Controllers should contain no business logic and no Model::query() calls.

// app/Containers/AppSection/User/UI/API/Routes/v1.php
Route::get('users/{id}', [Controller::class, 'findUserById'])->name('api.user.find');
// UI/API/Controllers/Controller.php
public function findUserById($id, Request $request)
{
    $user = $this->call(FindUserByIdAction::class, [$id]);

    return $this->transform($user, UserTransformer::class);
}
// Actions/FindUserByIdAction.php
class FindUserByIdAction extends Action
{
    public function __construct(private UserRepository $repository) {}

    public function run(string $id): User
    {
        return $this->repository->find($id);   // hash ID decoded by the repository/model trait
    }
}

$this->call() and $this->transform() are the Apiato idioms for invoking Actions and rendering Fractal output — use them consistently and the codebase stays uniform.

Repositories do the querying, Criteria do the slicing. Every repository exposes a RequestCriteria that transparently honours query strings, so a list endpoint becomes $this->repository->paginate() and clients get:

GET /v1/users?search=john&filter=status:active&orderBy=created_at&sortedBy=desc&limit=20&include=roles

Control what is reachable via $fieldSearchable on the repository and by writing custom Criteria classes in Data/Criterias/. This is the single biggest day-to-day leverage point: you get search, filtering, sorting, eager-loading and pagination for free, but only for the fields you deliberately expose.

Transformers (Fractal) are your API contract. Put defaultIncludes for cheap relations and availableIncludes behind ?include=. Response shape changes belong here, never in controllers.

Validation lives in ApiRequest::rules(); authorization in authorize() and policies. Apiato's base request already produces the standard error envelope ({"errors":[{status, code, title, detail, source}]}), so don't hand-roll validation — throw App\Ship\Exceptions\ValidationException / NotFoundException / CreateResourceFailedException and the handler maps them to HTTP codes.

Auth flow. The Authentication container ships a proxy-login endpoint that returns access_token / refresh_token; protect routes with the auth:api middleware and resolve the caller with $request->user(). For ownership rules use Apiato policies in Ship/Parents/Policies and check them in the Action, not the controller.

Testing. Tests live beside the container they cover (UI/API/Tests/Functional, Tests/Unit) and are picked up by the root PHPUnit config. Functional tests hit real routes:

$user = $this->createTestingUser();
$this->get('api/v1/users/' . $user->getHashedKey())->assertStatus(200);

Factories (Data/Factories), seeders and the test helpers in tests/_support are what make containers independently testable — a container with no test folder is a container nobody will dare change.

Cross-container reuse: import another container's Action or Repository directly. Only push something into app/Ship/ when it is genuinely infrastructure (base classes, shared middleware, global config), not "shared business logic".

Gotchas and Tips

  • Adoption reality: 3.1k stars but 0 dependents — that's not neglect, it's the shape of the project. Nobody requires a starter skeleton, so there is no upgrade path except diffing your fork against upstream hello-api. Pin a tag, keep your divergence in containers only, and re-diff before each Laravel bump.
  • v13.2.0 is cosmetic-only. The release is a single feature PR refreshing the generated UI (the docs page) to be modern and responsive; there are no API, config, or generator changes. Concretely: no upgrade work is required, but if you forked or restyled the docs view/template you now have a merge point — re-diff that view directory against 13.1.0 → 13.2.0 and port or skip it deliberately. Do not treat a composer update-style bump as safe just because the changelog looks small; this project has no vendor copy to bump.
  • Convention failures are silent. A controller outside UI/API/Controllers, a ServiceProvider outside Providers/, a route file that isn't Routes/v1.php — you get a missing route or an unresolvable binding with no error pointing at the layout. When something "doesn't exist", suspect the path before the code.
  • Hash ID trap. Route params and IDs in responses are hashids. $repository->find($id) decodes for you; raw where('id', $id) does not — you'll get empty results that look like a database bug. Decode explicitly when you leave the repository. Also: turning hash-id on after clients integrate silently breaks every stored ID.
  • RequestCriteria is always listening. Any column added to $fieldSearchable is now client-filterable, and ?search= across relations can turn into a full table scan. Review $fieldSearchable as a security surface, not boilerplate. Cap ?limit in config('repository.pagination') — the default maximum is generous enough to OOM a small box.
  • N+1 through transformers. defaultIncludes fires a query per model unless the relation is eager-loaded upstream. Push the with() into the repository or a Criterion, then measure with ?include= on a large limit.
  • Exception type = HTTP status. Throwing a plain \Exception gives you a generic 500 and a useless error envelope. Use the Ship exception classes so the handler can map them; register custom mappings in the exception handler rather than editing each Action.
  • Migrations across containers. Each container's migrations auto-run, but ordering is effectively arbitrary. Foreign keys between containers are fragile — prefer string/UUID keys, or keep cross-container relations minimal and add the constraint in a dedicated migration.
  • authorize() is easy to forget. A Request that validates perfectly and returns 201 does not prove authorization ran; write the 403 test explicitly. Same for route middleware — check php artisan route:list actually shows auth:api on the route you think it does.
  • Localization keys fail silently. Container language files (Languages/en/…) fall back to the key string on a typo; validation messages look "fine" until a user sees validation.custom.email.
  • Docs drift is real. The /docs page is generated from @api* docblock annotations (apidoc under the hood). A malformed annotation drops the endpoint from the docs without failing the build — treat doc regeneration as part of CI, not a nice-to-have. The 13.2.0 UI refresh makes the page nicer to read but does not change the annotation parser: a broken docblock is still invisible on the page and in the build log.
  • Debugging: php artisan route:list | grep users to confirm registration, config('apiato.hash-id') / config('repository.*') to confirm runtime settings after a config:cache, and clear caches (optimize:clear) whenever behaviour contradicts the source — cached config and routes are the usual culprit in this skeleton.
  • Extension points, in order of preference: custom Criteria → custom exceptions/handler mappings → subclass a Ship/Parents base class → middleware in Ship/Middlewares → fork the base class itself. Since there's no vendor/ copy, forking a parent class is legitimate — just record it, because it's the thing that will make your next upstream diff painful. Same applies to the docs view: if product wants the responsive layout plus your branding, fork it knowingly and note the file list you diverged on.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings