This walkthrough teaches the main Assegai workflow from a blank project to a working feature.
By the end, you will have:
- a running Assegai project
- an
ordersfeature generated by the CLI - DTOs for create and update input
- a working in-memory provider implementation
- a browsable
/docscontract for the feature
You do not need to know every part of Assegai first. The tutorial introduces the pieces as they become useful.
What you are building
Imagine a small restaurant kitchen screen.
The kitchen needs a simple API to:
- list orders
- create a new order
- update an existing order
- remove an order
That makes it a good tutorial app because it uses the workflow Assegai is best at:
- scaffold a feature
- keep controller code thin
- keep state and behavior in a provider
- let DTOs shape input
Step 1: Create the project
Create a new project:
assegai new kitchen-orders-api
cd kitchen-orders-api
Then start the server:
assegai serve
Open the app in the browser. At this point, the goal is simply to confirm that the project boots.
Step 2: Generate the first feature
Now generate an orders resource:
assegai g r orders
This command creates a feature module with the standard parts already wired together.
Depending on the current generator template, you should see a structure close to this:
src/Orders/
├── DTOs/
│ ├── CreateOrderDTO.php
│ └── UpdateOrderDTO.php
├── Entities/
│ └── OrderEntity.php
├── OrdersController.php
├── OrdersModule.php
└── OrdersService.php
This is one of Assegai's core ideas:
- the CLI does the repetitive structural work
- you fill in the feature-specific behavior
Step 3: Inspect the generated API surface
If your project is configured to export OpenAPI on demand, generate the latest spec:
assegai api:export openapi
Then open:
http://localhost:5000/docs
Even before the feature is finished, this is useful because it lets you inspect the shape of the endpoints the CLI scaffold created.
Step 4: Shape the DTOs
The generated DTOs are the place where request input becomes explicit.
Open CreateOrderDTO.php and make it describe the data needed to create a kitchen order:
<?php
namespace Assegaiphp\KitchenOrdersApi\Orders\DTOs;
use Assegai\Validation\Attributes\IsArray;
use Assegai\Validation\Attributes\IsNotEmpty;
use Assegai\Validation\Attributes\IsString;
class CreateOrderDTO
{
#[IsString]
#[IsNotEmpty]
public string $ticketNumber;
#[IsString]
#[IsNotEmpty]
public string $status;
#[IsArray]
public array $items = [];
}
Then give UpdateOrderDTO.php the same fields, but keep in mind that update DTOs often allow partial input.
The important lesson here is not just validation.
It is that a DTO gives the request body a clear shape that your controller and provider can rely on.
When this feature eventually moves to the ORM, that same DTO can usually be passed straight into repository->create($dto) or repository->update(..., $dto) because it is already a plain PHP object.
Step 5: Replace placeholder service behavior
The scaffold gives you the feature shape, but not your actual business behavior.
For a first tutorial, an in-memory array is enough.
Update OrdersService.php so it stores orders in memory:
<?php
namespace Assegaiphp\KitchenOrdersApi\Orders;
use Assegaiphp\KitchenOrdersApi\Orders\DTOs\CreateOrderDTO;
use Assegaiphp\KitchenOrdersApi\Orders\DTOs\UpdateOrderDTO;
class OrdersService
{
/**
* @var array<int, array<string, mixed>>
*/
private array $orders = [];
public function findAll(): array
{
return array_values($this->orders);
}
public function create(CreateOrderDTO $dto): array
{
$id = count($this->orders) + 1;
$order = [
'id' => $id,
'ticketNumber' => $dto->ticketNumber,
'status' => $dto->status,
'items' => $dto->items,
];
$this->orders[$id] = $order;
return $order;
}
public function update(int $id, UpdateOrderDTO $dto): ?array
{
if (!isset($this->orders[$id])) {
return null;
}
$this->orders[$id]['status'] = $dto->status ?? $this->orders[$id]['status'];
$this->orders[$id]['items'] = $dto->items ?? $this->orders[$id]['items'];
return $this->orders[$id];
}
}
This is not production persistence, and that is fine.
For the tutorial, the important thing is to feel the controller-service-DTO workflow clearly before the database enters the picture.
Step 6: Keep the controller thin
The generated controller should already be close to the right shape.
Keep the controller focused on HTTP concerns:
- route
- params
- body binding
- return value
That usually looks something like this:
#[Post]
public function create(#[Body] CreateOrderDTO $dto): array
{
return $this->ordersService->create($dto);
}
That one method shows a lot of what Assegai is aiming for:
- the route is close to the handler
- the DTO is explicit
- the controller does not contain the application logic
Step 7: Try the feature
Now test the flow.
Create an order:
POST /orders
Content-Type: application/json
{
"ticketNumber": "KDS-1001",
"status": "pending",
"items": ["Burger", "Fries"]
}
Then list orders:
GET /orders
If you exported the OpenAPI document again, /docs should now reflect the DTO-backed request shape more clearly.
Step 8: What you just learned
Without adding a database yet, you have already used Assegai's main workflow:
- scaffold with the CLI
- let modules and generated structure define the feature boundary
- let DTOs shape request input
- keep controller methods thin
- keep behavior in a provider
- inspect the contract through generated docs
That is the core loop many Assegai apps repeat feature after feature.
When you switch this feature over to the ORM, keep three practical defaults in mind:
- pass DTOs straight into
create()andupdate()unless you genuinely need to reshape the data first - prefer
save()as the default write path;insert()is still available, butsave()is the smoother day-to-day choice for most feature code - prefer
softRemove(...)for deletes, since entities already carryChangeRecorderTrait
Step 9: Where to go next
Now you have two good next steps:
- add persistence with the ORM guides
- add authentication once the orders API should belong to real users
If you want persistence next, continue with Data and ORM.
If you want auth next, continue with Authentication.