A scalable RESTful Web API for managing user data, built with PHP and Laravel, using Eloquent ORM for persistence and PostgreSQL as the relational database.
This project demonstrates core engineering practices — layered architecture, REST conventions, validation, and clean persistence boundaries — applied in the PHP/Laravel ecosystem. Built with GitHub Spec Kit spec-driven development.
- RESTful CRUD operations for a
usersresource - Layered architecture (controller → service → model)
- PostgreSQL persistence with Eloquent ORM and migrations
- BCrypt password hashing (
passwordHashin JSON responses) - Role-based user model (
USER,ADMIN) - Automatic
created_attimestamp on user creation - OpenAPI documentation at
/api/docs/ - Docker Compose for one-command local setup
- PHPUnit feature and unit tests
| Layer | Technology |
|---|---|
| Language | PHP 8.4+ |
| Framework | Laravel 13 |
| ORM | Eloquent |
| Database | PostgreSQL 16 |
| API Docs | OpenAPI 3 (Swagger UI) |
| Containers | Docker Compose |
| Spec workflow | GitHub Spec Kit |
Table: users
| Column | Type | Notes |
|---|---|---|
id |
BIGINT, PK | Auto-increment primary key |
name |
VARCHAR(255) | User display name |
email |
VARCHAR(255) | Unique email |
password |
VARCHAR(255) | BCrypt hash; exposed as passwordHash in JSON |
role |
VARCHAR(10) | USER or ADMIN |
created_at |
TIMESTAMPTZ | Set at creation time |
- Docker installed and running (
docker infosucceeds) - Git
Optional (for running without Docker):
- PHP 8.4+ with
pdo_pgsqlextension - Composer 2
- PostgreSQL 16+
git clone <repo-url> php-api
cd php-apicp .env.example .env
php artisan key:generate # skip if using Docker defaultsDefault values work for local Docker development.
docker compose up --buildThis will:
- Start PostgreSQL 16 and create the
PhpApidatabase - Build the Laravel API image
- Wait for PostgreSQL to become healthy
- Run migrations (creates the
userstable) - Start the API on port 8080
The API is available at http://localhost:8080.
docker compose down # stop containers (data persists in volume)
docker compose down -v # stop and remove database volume| Method | Endpoint | Description |
|---|---|---|
| GET | /api/users |
List all users |
| GET | /api/users/{id} |
Get user by ID |
| POST | /api/users |
Create a new user |
| PUT | /api/users/{id} |
Update a user |
| DELETE | /api/users/{id} |
Delete a user |
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "your-secure-password",
"role": "USER"
}'curl http://localhost:8080/api/userspassword is optional on update; omit it to keep the existing hash.
curl -X PUT http://localhost:8080/api/users/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Smith",
"email": "jane.smith@example.com",
"password": "new-password",
"role": "ADMIN"
}'curl -X DELETE http://localhost:8080/api/users/1Returns HTTP 204 with no body.
- Swagger UI: http://localhost:8080/api/docs/
- OpenAPI schema: http://localhost:8080/api/schema/
- Design contract:
specs/001-users-api/contracts/users-api.openapi.yaml
| Variable | Default (Docker) | Description |
|---|---|---|
DB_HOST |
postgres |
PostgreSQL hostname |
DB_PORT |
5432 |
PostgreSQL port |
DB_DATABASE |
PhpApi |
Database name |
DB_USERNAME |
postgres |
PostgreSQL user |
DB_PASSWORD |
postgres |
PostgreSQL password |
APP_KEY |
(generate) | Laravel encryption key |
APP_DEBUG |
true |
Debug mode |
Tests use PHPUnit with an in-memory SQLite database — no PostgreSQL or Docker required.
composer install
php artisan testTest suites:
| File | Covers |
|---|---|
tests/Unit/UserServiceTest.php |
Business logic, password hashing, duplicate email |
tests/Feature/UserApiTest.php |
Full CRUD HTTP endpoints |
php-api/
├── app/
│ ├── Http/Controllers/Api/ # REST controllers
│ ├── Http/Requests/ # Input validation
│ ├── Http/Resources/ # Response DTOs
│ ├── Models/User.php # Eloquent entity
│ └── Services/UserService.php # Business logic
├── database/migrations/
├── routes/api.php
├── specs/001-users-api/ # Spec Kit feature artifacts
├── Dockerfile
├── docker-compose.yml
├── entrypoint.sh
└── .env.example
Layered architecture:
| Layer | PHP | Java (reference) |
|---|---|---|
| HTTP | Controllers/Api/ |
controller/ |
| Business logic | Services/ |
service/ |
| Data access | Models/ |
repository/ + JPA entity |
| DTOs | Requests/ + Resources/ |
dto/ |
- Passwords are hashed with BCrypt before being saved to the database
GET /api/usersandGET /api/users/{id}may includepasswordHashin responses (demo/debug)POSTandPUTsuccess responses never echo plain-textpassword- Input validation is enforced on all write operations
- No authentication on endpoints in v1 (matches Java demo)
- Use HTTPS in production
MIT License.
Author: Lucas Soares