api-starter-kit maintained by laraveldaily
Laravel API Starter Kit
A Laravel API-only starter kit: token-based authentication and account management as plain, readable Laravel code. No front-end, no Vite, no Fortify — just controllers, Form Requests, API Resources, and a green test suite.
- Token auth via Laravel Sanctum personal access tokens — DB-backed, revocable, works for mobile, SPA, and CLI clients.
- Code-based password reset — the API emails a 6-digit code, the client posts
it back. No reset links, no
FRONTEND_URL, no assumption about your front end. - OpenAPI docs via Scramble at
/docs/api. - Fully tested — a Pest feature test for every endpoint;
php artisan testis green immediately after install.
Installation
laravel new my-app --using=laraveldaily/api-starter-kit --no-node
--no-node skips the installer's npm install/build prompt — this kit ships no
front-end, so there is nothing to build. (If you forget the flag, just answer
"No" when asked.) When prompted for a testing framework, pick Pest — the
kit's test suite is already written with it.
Or with Composer directly (no prompts at all):
composer create-project laraveldaily/api-starter-kit my-app
Either way you get a generated APP_KEY, an SQLite database, and migrated
tables out of the box. Start the server with:
php artisan serve
The mailer defaults to MAIL_MAILER=log, so password reset codes land in
storage/logs/laravel.log — the whole auth flow works with zero mail setup.
Endpoints
All routes live under /api/v1. Authenticated routes expect an
Authorization: Bearer <token> header. All responses (including errors) are JSON.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /api/v1/register |
– | Create an account, receive a token |
| POST | /api/v1/login |
– | Issue a token for valid credentials |
| POST | /api/v1/forgot-password |
– | Email a 6-digit reset code |
| POST | /api/v1/reset-password |
– | Set a new password using the code |
| POST | /api/v1/logout |
✓ | Revoke the current token |
| GET | /api/v1/user |
✓ | Get the authenticated user |
| PUT | /api/v1/user |
✓ | Update name and/or email |
| PUT | /api/v1/user/password |
✓ | Change password (revokes other tokens) |
| DELETE | /api/v1/user |
✓ | Delete the account (password confirmed) |
| GET | /api/v1/tokens |
✓ | List active tokens |
| DELETE | /api/v1/tokens/{id} |
✓ | Revoke one token |
| DELETE | /api/v1/tokens |
✓ | Revoke all tokens except the current one |
Register
device_name is optional everywhere a token is issued; it defaults to api.
curl -X POST http://localhost:8000/api/v1/register \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "secret-password",
"password_confirmation": "secret-password",
"device_name": "iphone-15"
}'
{
"token": "1|x6tLxhBuYDQwGesgZQGMqLpdo1Wt8h9dLn1to2hX",
"token_type": "Bearer",
"user": {
"id": 1,
"name": "Jane Doe",
"email": "jane@example.com",
"created_at": "2026-06-11T12:00:00.000000Z",
"updated_at": "2026-06-11T12:00:00.000000Z"
}
}
Login
curl -X POST http://localhost:8000/api/v1/login \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{"email": "jane@example.com", "password": "secret-password"}'
Returns the same { token, token_type, user } shape as register. Invalid
credentials return 422 with an email error. Login is throttled to 5
attempts per minute per email + IP.
Logout
Revokes the token used to authenticate the request.
curl -X POST http://localhost:8000/api/v1/logout \
-H "Accept: application/json" \
-H "Authorization: Bearer <token>"
Returns 204 No Content.
Forgot password
curl -X POST http://localhost:8000/api/v1/forgot-password \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{"email": "jane@example.com"}'
{ "message": "If the email exists, a reset code has been sent." }
The response is identical whether or not the account exists, so callers cannot
enumerate emails. When the account exists, a 6-digit code is emailed (logged,
by default) and is valid for 15 minutes (config/auth.php →
passwords.users.expire).
Reset password
curl -X POST http://localhost:8000/api/v1/reset-password \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{
"email": "jane@example.com",
"code": "123456",
"password": "new-secret-password",
"password_confirmation": "new-secret-password"
}'
{ "message": "Password has been reset." }
A successful reset deletes the code (single use) and revokes every token
the user holds. A missing, expired, or wrong code always returns the same
generic 422:
{ "message": "...", "errors": { "code": ["The reset code is invalid or has expired."] } }
Brute-force note: the 6-digit code is protected by throttling (5 requests per minute per email + IP), the 15-minute expiry, and single-use deletion. Tighten further (longer code, attempt counter) if your threat model needs it.
Get the current user
curl http://localhost:8000/api/v1/user \
-H "Accept: application/json" \
-H "Authorization: Bearer <token>"
{
"data": {
"id": 1,
"name": "Jane Doe",
"email": "jane@example.com",
"created_at": "2026-06-11T12:00:00.000000Z",
"updated_at": "2026-06-11T12:00:00.000000Z"
}
}
Update profile
Send only the fields you want to change. Emails are normalized to lowercase.
curl -X PUT http://localhost:8000/api/v1/user \
-H "Content-Type: application/json" -H "Accept: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"name": "Jane D.", "email": "jane.d@example.com"}'
Returns the updated user. Email changes take effect immediately — there is no verification flow in v1 (see Future add-ons).
Change password
curl -X PUT http://localhost:8000/api/v1/user/password \
-H "Content-Type: application/json" -H "Accept: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"current_password": "secret-password",
"password": "new-secret-password",
"password_confirmation": "new-secret-password"
}'
{ "message": "Password updated." }
Every other token is revoked; the token that made the request keeps working.
Delete account
curl -X DELETE http://localhost:8000/api/v1/user \
-H "Content-Type: application/json" -H "Accept: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"password": "secret-password"}'
Returns 204 No Content. All tokens are revoked and the user row is deleted.
List tokens
curl http://localhost:8000/api/v1/tokens \
-H "Accept: application/json" \
-H "Authorization: Bearer <token>"
{
"data": [
{
"id": 2,
"name": "iphone-15",
"abilities": ["*"],
"last_used_at": "2026-06-11T12:34:56.000000Z",
"created_at": "2026-06-11T12:00:00.000000Z"
}
]
}
Token hashes are never exposed.
Revoke one token
curl -X DELETE http://localhost:8000/api/v1/tokens/2 \
-H "Accept: application/json" \
-H "Authorization: Bearer <token>"
Returns 204 No Content, or 404 when the token does not exist or belongs to
another user.
Revoke all other tokens ("log out everywhere")
curl -X DELETE http://localhost:8000/api/v1/tokens \
-H "Accept: application/json" \
-H "Authorization: Bearer <token>"
Returns 204 No Content. Every token except the current one is revoked.
Error shapes
All /api/* errors render as JSON:
| Status | Body |
|---|---|
| 422 validation | { "message": "...", "errors": { "field": ["..."] } } |
| 401 unauthenticated | { "message": "Unauthenticated." } |
| 404 not found | { "message": "..." } |
| 429 throttled | { "message": "Too Many Attempts." } + Retry-After header |
| 500 | { "message": "Server Error." } (trace only with APP_DEBUG=true) |
Rate limiting
Defined in app/Providers/AppServiceProvider.php:
| Limiter | Limit | Applied to |
|---|---|---|
api |
60/min per user (or IP) | everything else |
login |
5/min per email + IP | /login |
password |
5/min per email + IP | /forgot-password, /reset-password |
API documentation
Interactive OpenAPI 3.1 docs are generated from the code by Scramble:
GET /docs/api— UIGET /docs/api.json— OpenAPI document
Both are restricted to the local environment by the viewApiDocs gate in
app/Providers/AppServiceProvider.php; relax that gate to expose them elsewhere.
Token expiration
Tokens do not expire by default. Set expiration (minutes) in
config/sanctum.php to give every token a lifetime.
CORS
config/cors.php allows all origins for api/* — safe for token auth because
no cookies or credentials are involved. Pin allowed_origins in production if
you prefer.
Development
composer dev # serve + queue + logs (requires npx for concurrently)
composer test # pint --test, phpstan, pest
Future add-ons
Deliberately not in v1, and designed to bolt on cleanly:
- Email verification — re-add
MustVerifyEmailtoUserplus the verify/resend endpoints; theemail_verified_atcolumn already exists. - Two-factor authentication (TOTP + recovery codes).
- Social login via Laravel Socialite.
- OAuth2 via Laravel Passport (third-party authorization).
- Teams / organizations, roles & permissions.
License
The MIT License.