Custom endpoints
Add endpoints to an interface, write ApiEndpoint classes, and group them in resources.
Custom endpoints live on an interface, so they share its path, middleware, and gate.
Quick endpoints
String keys passed to endpoints() are registered as GET routes inside the interface group:
use Streams\Api\ApiInterface;
use Streams\Api\Support\Facades\API;
API::interface(
ApiInterface::make('api')
->path('api')
->endpoints([
'health' => fn () => response()->json(['status' => 'ok']),
'stats' => \App\Http\Controllers\StatsController::class,
])
);
// GET /api/health, GET /api/stats
Endpoint classes
Extend Streams\Api\Builders\Endpoints\ApiEndpoint, implement __invoke(), and return ApiResponse::make(). The static route() method returns an EndpointRouter you can pass to endpoints() or a resource:
namespace App\Api;
use Illuminate\Http\JsonResponse;
use Streams\Api\ApiResponse;
use Streams\Api\Builders\Endpoints\ApiEndpoint;
class FeaturedPosts extends ApiEndpoint
{
public function __invoke(): JsonResponse
{
$response = new ApiResponse('posts');
$criteria = $response->stream->entries();
$this->fire('apply', compact('criteria'));
return $response->make(
$criteria->where('featured', true)->limit(10)->get()
);
}
}
->endpoints([
FeaturedPosts::route('posts/featured', 'get', 'posts.featured'),
])
route(string $path, string|array $methods = 'get', ?string $routeName = null, array $where = []) registers the route when the interface mounts. Endpoint instances also accept routeMiddleware() and withoutRouteMiddleware().
Firing apply keeps your endpoint compatible with listeners such as tenant scoping.
Resources
Group related endpoints in an ApiResource. Endpoint names are appended to the resource's route name prefix:
namespace App\Api;
use Streams\Api\ApiResource;
class PostsResource extends ApiResource
{
protected static ?string $slug = 'posts';
protected static string|array $middleware = ['auth:sanctum'];
public static function getEndpoints(): array
{
return [
'featured' => FeaturedPosts::route('featured', 'get'),
];
}
}
With usesRoutePrefix() returning true (the default), routes are prefixed with the slug: GET /api/posts/featured, named streams.api.posts.featured. EntriesResource and StreamsResource return false because their paths already start with streams/.
Override a built-in endpoint
Subclass a built-in endpoint and point a resource subclass at it:
use Streams\Api\Endpoints\Entries\CreateEntry;
use Streams\Api\Resources\EntriesResource;
class AuditedCreateEntry extends CreateEntry
{
public function __invoke(string $stream): \Illuminate\Http\JsonResponse
{
$response = parent::__invoke($stream);
\Illuminate\Support\Facades\Log::info("API created an entry in {$stream}");
return $response;
}
}
class AppEntriesResource extends EntriesResource
{
public static function getEndpoints(): array
{
return [
...parent::getEndpoints(),
'create' => AuditedCreateEntry::route('streams/{stream}/entries', 'post'),
];
}
}