Skip to content

Add stateless API starter kit#78

Open
WendellAdriel wants to merge 27 commits into
mainfrom
feat/api-starter-kit
Open

Add stateless API starter kit#78
WendellAdriel wants to merge 27 commits into
mainfrom
feat/api-starter-kit

Conversation

@WendellAdriel

@WendellAdriel WendellAdriel commented Apr 15, 2026

Copy link
Copy Markdown
Member

Overview

Maestro ships starter kits for every flavor of Laravel app, but there was no option for teams building a pure JSON API. This adds a new API variant so backend-only projects can start from the same orchestrated foundation as the Inertia and Livewire kits.

About the Kit

The kit is stateless and built on Sanctum, with endpoints for register, login, logout, current user, profile and password updates, email verification, token refresh, and account deletion. It also uses JSON:API resources.

Scramble generates the API docs and serves them at /docs using Scalar. Account deletion and password updates are gated behind the verified middleware to match the Fortify kits, while profile edits stay open so unverified users can fix a bad email.

Orchestrator Updates

Orchestrator support comes along with it: BuildCommand, the StarterKit enum, the kit manifest, and the check/kit helpers all learn about the new variant, and CI plus the push workflow now include API in the matrix.

The README, CLAUDE.md, AGENTS.md, and SKILL.md were updated to reflect the new variant count (22) and the --api flag.

@pushpak1300 pushpak1300 self-requested a review April 24, 2026 09:38
Comment thread kits/API/Base/routes/api.php Outdated
Comment thread kits/API/Base/tests/Unit/ExampleTest.php Outdated
Comment thread kits/API/Base/app/Http/Controllers/Auth/VerifyEmailController.php Outdated
Comment thread kits/API/Base/routes/api.php
Comment thread kits/API/Base/app/Http/Requests/Auth/RegisterRequest.php Outdated
Comment thread kits/API/Base/.env.example
Comment thread kits/API/Base/app/Http/Controllers/Auth/RefreshTokenController.php Outdated

@pushpak1300 pushpak1300 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM !

@joetannenbaum joetannenbaum left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really good! Just a couple of notes.

Comment thread kits/API/Base/app/Http/Controllers/Auth/LoginController.php Outdated

event(new Registered($user));

return response()->json([

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we returning a user resource here along with the token?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's needed, TBH.
We already have the /me endpoint for getting the user info.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, not sure I agree here. I can get on board with that for login, but for register why don't we just do something like this? Reduces round trips and gives the client the information they definitely need anyway.

(new UserResource($user))
    ->additional(['token' => $token])
    ->response()
    ->setStatusCode(Response::HTTP_CREATED);

Comment thread kits/API/Base/app/Http/Controllers/Auth/LoginController.php
);

if ($user->hasVerifiedEmail()) {
return response()->json(['message' => __('Email already verified.')]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be overkill, but do we want to macro message, successMessage, etc? Or too much?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might be a good idea.
I'll try it out so we can check how it will look like

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @joetannenbaum

I think this is a good idea to extract into a helper, because it's something that can be heavily used in the codebase when starting to implement something with the Starter Kit, however, I think the macro is not the best solution. Because it can be a little confusing, and depending on what devs are using, they may not even get autocomplete for it.

I think we can do it in either of these ways:

  1. A response class implementing the Illuminate\Contracts\Support\Responsable interface
  2. A trait - HasApiResponses (or something similar) - with a method messageResponse (or something similar)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with response class, makes for better extendibility.

Comment thread kits/API/Base/routes/api.php Outdated
Comment thread kits/Livewire/Blank/.github/workflows/lint.yml
Comment thread kits/Inertia/WorkOS/Base/composer.json
Comment thread kits/Livewire/Blank/.github/workflows/tests.yml
Comment thread kits/API/Base/app/Http/Controllers/Auth/PasswordUpdateController.php Outdated
]);
}

$token = $user->createToken('auth')->plainTextToken;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to give them the opportunity to name this in case they want to list tokens and identify them? Like an optional device_name field with the auth fallback value?

@WendellAdriel WendellAdriel May 18, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, I think we can use this auth, and if people need they can extend it, what do you think?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno, seems pretty cheap to just do something like $request->string('device_name', 'auth'), why not?

@joetannenbaum joetannenbaum left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some responses to our current conversations.

Two additional things:

  • We're mixing JsonApiResource and flat JSON responses, feels funny. We should go one or the other, yeah? Or, probably worse idea, allow the user to choose the format at installation time?
  • I think we talked about it and may have tabled it, but should we offer/scaffold anything around versioning in the kit?

@WendellAdriel WendellAdriel force-pushed the feat/api-starter-kit branch from dffbd39 to 153376d Compare May 20, 2026 16:54
@WendellAdriel WendellAdriel force-pushed the feat/api-starter-kit branch from a9f93c2 to b4554a3 Compare June 3, 2026 11:34
@WendellAdriel WendellAdriel marked this pull request as ready for review June 3, 2026 12:02
@RasmusPJustesen

Copy link
Copy Markdown

Exciting to see this as ready for review! ⭐

@WendellAdriel

Copy link
Copy Markdown
Member Author

@RasmusPJustesen we're getting there! 🚀

BuildCommand, StarterKit enum, kit-manifest, check-kits, and
kit-helpers updated to handle the new API kit. GitHub Actions
workflows include the API variant in test and push matrices.
README, CLAUDE.md, AGENTS.md, and SKILL.md reflect the new
variant count (22) and --api flag.
Ships kits/API/Base, a stateless JSON API layer on top of
Shared/Blank and Shared/Base.

- Sanctum auth flow: register, login (rate limited), logout, current user
- Profile and password updates as PATCH endpoints
- Email verification with signed URLs and resend notification
- JSON:API-style responses via a single UserResource
- Scribe-driven docs configured entirely with PHP attributes
- Response messages wrapped in __() so kits can localize them
- Feature tests covering every endpoint plus Unit/Feature ExampleTests
- Add DELETE /user account deletion endpoint requiring the current
  password; tokens and user are removed inside a transaction
- Add POST /token/refresh to rotate the current Sanctum token
- Drop the /api URL prefix and redirect / to /up
- Give every API route an explicit name and migrate tests to route()
  helpers
- Wrap register and refresh DB writes in transactions; dispatch the
  Registered event after commit
Mirror the Inertia/Livewire Fortify kits: unverified users can still
log in, view, and update their profile (to fix their email), but
cannot delete their account or change their password. Adds two
regression tests that skip when MustVerifyEmail is not enabled.
Without this file, the watcher in orchestrator/scripts/watch.js had no
source of vendor/node_modules ignore patterns for the API layer stack
and would sync build/vendor/** back into kits/API/Base on live change
events.
Allow signed API verification links to succeed without a bearer token while keeping API-only matrix runs separate from Fortify variants.

Made-with: Cursor
- Throttle the register endpoint at 5/min to match login
- Validate the verify-email hash before exposing already-verified
  state, closing the unauthenticated account-probe vector
- Preserve the original token name and abilities on refresh instead
  of resetting both to 'auth' and ['*']
- Use fluent Rule::unique(User::class) in RegisterRequest for parity
  with ProfileUpdateRequest
- Use Tests\TestCase in tests/Unit/ExampleTest so the unit suite
  uses the kit's bootstrap
@WendellAdriel WendellAdriel force-pushed the feat/api-starter-kit branch from b4554a3 to c060ddc Compare June 12, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants