A clean, RESTful Task Management API built with Laravel 13
Taskify is a production-ready task management API with token-based authentication, per-user task & project isolation, filtering, and rate limiting — fully documented with interactive OpenAPI docs.
- 🔐 Sanctum token auth — 24-hour token expiry, revocable via logout
- 📝 Full CRUD with soft delete & restore
- 🔍 Smart filtering — exact
status/priority/project_idfilters + case-insensitive search, plus?sort=ordering and?per_page=pagination - 📊 Statistics —
GET /tasks/statsaggregates by status/priority, overdue, due-today, and completed-this-week - 👤 Per-user isolation — tasks and projects are private to their owner
- 📁 Projects — organize tasks into color-coded projects with full CRUD, soft delete & restore
- 🚦 Rate limiting — auth (5/min) & API (60/min)
- ✅ CI pipeline — Pint style checks + Pest test suite across SQLite & PostgreSQL
- 📚 Interactive docs via Scramble
- 🗃️ PostgreSQL + Docker setup included
- Requirements
- Installation
- Running Tests
- Project Structure
- API Reference
- Authentication
- Tasks
- Projects
- Status Codes
- Rate Limits
- Documentation
- Deployment
- License
- Developed By
| Tool | Version |
|---|---|
| PHP | 8.3+ |
| Composer | latest |
| PostgreSQL | 16 (or any Laravel-supported DB) |
| Node.js | optional (frontend assets only) |
git clone https://github.com/MahdiiMax/taskify.git taskify
cd taskify
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate
php artisan serve💡 Tip: run
composer run setupto do all of the above automatically.
A docker-compose.yaml spins up PostgreSQL (db taskify, user/password admin/admin) and pgAdmin at http://localhost:5050 (login: admin@admin.com / admin):
docker compose up -dThen configure .env:
DB_CONNECTION=pgsql
DB_DATABASE=taskify
DB_USERNAME=admin
DB_PASSWORD=admin
AUTH_GUARD=sanctum
SANCTUM_EXPIRATION=1440| Key | Purpose |
|---|---|
AUTH_GUARD=sanctum |
Makes $request->user() resolve API tokens |
SANCTUM_EXPIRATION=1440 |
Token lifetime in minutes (24h) |
composer test # or: php artisan testThe suite runs on in-memory SQLite — no DB server needed.
Run the suite against a real PostgreSQL server (adjust the credentials in phpunit.pgsql.xml to match your database):
vendor/bin/pest --configuration=phpunit.pgsql.xmlapp/
├── Enums/ # TaskPriority, TaskStatus, ProjectColor
├── Http/
│ ├── Controllers/Api/V1/ # AuthController, TaskController, ProjectController
│ ├── Middleware/Api/V1/ # GuestMiddleware
│ ├── Requests/Api/V1/ # Auth/, Task/ & Project/ Form Requests
│ └── Resources/Api/V1/ # TaskResource, UserResource, ProjectResource
├── Models/ # Task, User, Project
├── Policies/ # TaskPolicy, ProjectPolicy
└── Providers/ # AppServiceProvider
routes/
└── api.php # v1 API routes
tests/
├── Feature/Api/V1/ # AuthTest, TaskTest, ProjectTest
└── phpunit.pgsql.xml # PostgreSQL test configuration (CI)
| Base URL | http://localhost:8000/api/v1 |
|---|---|
| Interactive docs | http://localhost:8000/docs/api |
All task and project endpoints require a Bearer token:
Authorization: Bearer {token}
Tokens are issued by login, live 24 hours, and are revoked by logout.
{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "password123",
"password_confirmation": "password123"
}
201→{ "message", "user": { "id", "name", "email", "created_at" } }
{
"email": "jane@example.com",
"password": "password123"
}
200→{ "message", "user": {...}, "token": "<plain token>" }
200→{ "message": "logged out successfully" }(revokes the current token)
| Method | Endpoint | Description |
|---|---|---|
| GET | /tasks |
List own tasks (paginated, 10/page) |
| POST | /tasks |
Create a task |
| GET | /tasks/{task} |
Show a task |
| PUT/PATCH | /tasks/{task} |
Update a task |
| DELETE | /tasks/{task} |
Soft delete a task |
| GET | /tasks/trashed |
List soft-deleted tasks |
| GET | /tasks/stats |
Aggregate statistics for your tasks |
| POST | /tasks/{task}/restore |
Restore a soft-deleted task |
| Field | Rules |
|---|---|
title |
required · string · max 255 |
description |
nullable · string |
status |
nullable — pending · in_progress · done |
priority |
nullable — low · medium · high |
due_date |
nullable · date · today or later |
project_id |
nullable · exists in your projects |
| Query | Example | Behavior |
|---|---|---|
status |
?status=pending |
exact match |
priority |
?priority=high |
exact match |
search |
?search=buy milk |
case-insensitive partial match on title/description |
project_id |
?project_id=3 |
exact match (your projects only) |
per_page |
?per_page=25 |
page size 1–100, default 10 |
sort |
?sort=-due_date,title |
whitelist: title, status, priority, due_date, created_at · - prefix = descending |
Invalid status/priority values → 422.
| Method | Endpoint | Description |
|---|---|---|
| GET | /projects |
List own projects (paginated, 10/page) |
| POST | /projects |
Create a project |
| GET | /projects/{project} |
Show a project |
| PUT/PATCH | /projects/{project} |
Update a project |
| DELETE | /projects/{project} |
Soft delete a project |
| GET | /projects/trashed |
List soft-deleted projects |
| POST | /projects/{project}/restore |
Restore a soft-deleted project |
| Field | Rules |
|---|---|
name |
required · string · max 255 |
description |
nullable · string |
color |
nullable — white · black · blue · pink · red · green · yellow · orange |
| Code | Meaning |
|---|---|
200 |
Success |
201 |
Created |
204 |
Deleted (empty response body) |
400 |
Already authenticated (on login/register) |
401 |
Unauthenticated / expired or revoked token |
403 |
Forbidden (another user's resource) |
422 |
Validation error |
429 |
Rate limit exceeded |
| Scope | Limit |
|---|---|
| Auth routes (login/register) | 5/min per email + IP |
| All API routes | 60/min per user (or IP) |
Interactive API docs rendered with Stoplight Elements (dark theme):
| Docs UI | http://localhost:8000/docs/api |
|---|---|
| OpenAPI spec | http://localhost:8000/docs/api.json (OpenAPI 3.1, served live) |
Export the spec to a file:
php artisan scramble:export # writes api.jsonapi.json is a standard OpenAPI document — import it into Insomnia or Postman to explore and test the API.
- Run
php artisan config:cache/route:cacheonly in production — in development a stale config cache can serve outdated settings. - Warm the docs cache with
php artisan scramble:cache. - Expired tokens stay in the DB until pruned — schedule
php artisan sanctum:prune-expired(daily viaroutes/console.php+ theschedule:runcron) to keep the table clean.
This project is open-sourced under the MIT License — © 2026 Mahdi Sadeghi.
Mahdi Sadeghi
Full-Stack Developer
Built with ❤️ and ☕ using Laravel