Simple University API is a Laravel 12 REST API for managing an academic environment with users, professors, students, courses, and course enrollments.
The API uses JWT bearer authentication and restricts write operations to administrator users.
- User registration and login with JWT authentication
- Student CRUD
- Professor CRUD
- Course CRUD
- Student enrollment in courses
- Professor assignment to courses
- Paginated list endpoints
- OpenAPI documentation generated with Scramble
- Pest/PHPUnit test setup
- PHP 8.2+
- Laravel 12
- MySQL 8.4
- JWT Auth (
php-open-source-saver/jwt-auth) - Laravel Sanctum installed as a project dependency
- Scramble OpenAPI documentation
- Pest
- Vite
- Laravel Sail
This project follows a layered architecture to keep responsibilities separated and make the API easier to maintain, test, and evolve.
The main layers are:
| Layer | Responsibility |
|---|---|
| Controllers | Handle HTTP requests, validate input, check authorization, and return API responses |
| Services | Hold business rules and coordinate use cases |
| Repositories | Encapsulate database access and Eloquent queries |
| Models | Represent database entities and relationships |
| Exceptions | Represent domain errors in a reusable way |
| Helpers | Provide shared utility behavior, such as validation helpers and exception response formatting |
The API has business rules that go beyond simple CRUD operations, such as checking whether a student is already enrolled in a course, validating professor assignment rules, validating CPF values, and restricting write operations to administrator users.
Keeping these rules in service classes prevents controllers from becoming too large and keeps the HTTP layer focused on request and response handling.
Controllers are responsible for receiving requests, applying Laravel validation rules, checking whether the authenticated user has admin permissions when needed, and delegating the actual use case to a service.
They do not directly implement persistence rules. This keeps the controller layer thin and focused on the API contract.
Services contain the core business logic of the application.
Examples:
StudentServiceImplvalidates email uniqueness and gender before creating or updating students.ProfessorServiceImplvalidates email uniqueness, CPF uniqueness, CPF format, and professor data.CourseServiceImplhandles course creation, student enrollment, student unenrollment, professor assignment, and professor removal.
This makes business behavior reusable and easier to test independently from HTTP concerns.
Repositories centralize database access.
Instead of spreading Eloquent queries across controllers and services, repositories provide methods such as findById, findAll, create, delete, and existence checks.
This keeps persistence logic in one place and makes future changes to query behavior easier to manage.
The project uses custom exceptions for domain errors, such as:
StudentNotFoundExceptionCourseNotFoundExceptionEmailAlreadyRegisteredExceptionStudentAlreadyEnrolledExceptionProfessorAlreadyEnrolledException
These exceptions make failure cases explicit and allow the API to return consistent error responses through GlobalExceptionHandler.
The project uses PHP enums for fixed domain values:
GenderLanguageLevelStatus
Enums avoid duplicated string values across the codebase and make accepted values easier to understand and maintain.
Authentication is handled with JWT bearer tokens using the api guard.
Authorization is intentionally simple: authenticated users can access read endpoints, while write and relationship-management endpoints require is_admin = true.
For a local setup:
- PHP 8.2 or newer
- Composer
- Node.js and npm
- MySQL
For a Docker setup:
- Docker
- Docker Compose
Create your environment file from the example:
cp .env.example .envImportant variables:
APP_NAME="Simple University API"
APP_URL=http://localhost
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=simple_university_api
DB_USERNAME=root
DB_PASSWORD=
PAGINATION_SIZE=20
JWT_SECRET=Generate the Laravel application key and JWT secret:
php artisan key:generate
php artisan jwt:secretInstall PHP and JavaScript dependencies:
composer install
npm installCreate the local MySQL database if it does not exist yet:
CREATE DATABASE simple_university_api;Run the database migrations:
php artisan migrateOptional: run the database seeder:
php artisan db:seedBuild frontend assets:
npm run buildStart the Laravel development server:
php artisan serveThe API will be available at:
http://127.0.0.1:8000/api
You can also run the full development stack configured in composer.json:
composer run devThis starts the Laravel server, queue listener, logs, and Vite.
Install dependencies first if they are not available:
composer installStart the Docker containers:
./vendor/bin/sail up -dRun migrations:
./vendor/bin/sail artisan migrateGenerate the keys if needed:
./vendor/bin/sail artisan key:generate
./vendor/bin/sail artisan jwt:secretThe API will be available at:
http://localhost/api
This API uses JWT bearer tokens.
Register a user:
POST /api/auth/registerRequest body:
{
"name": "Admin User",
"email": "admin@example.com",
"password": "password123",
"password_confirmation": "password123"
}Login:
POST /api/auth/loginRequest body:
{
"email": "admin@example.com",
"password": "password123"
}Successful response:
{
"access_token": "jwt-token",
"token_type": "bearer",
"expires_in": 3600
}Use the token in authenticated requests:
Authorization: Bearer jwt-tokenCreating, updating, deleting, enrolling, and unenrolling resources requires an authenticated user with is_admin = true.
New users are created with is_admin = false by default. To promote a user locally, use Tinker:
php artisan tinker$user = App\Models\User::where('email', 'admin@example.com')->first();
$user->is_admin = true;
$user->save();With Sail:
./vendor/bin/sail artisan tinkerScramble is configured for this project. After starting the application, the generated API documentation is available at:
http://127.0.0.1:8000/docs/api
The exported OpenAPI path is configured as:
api.json
All endpoints below use the /api prefix.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /auth/register |
No | Register a new user |
| POST | /auth/login |
No | Login and receive a JWT |
| POST | /auth/me |
Yes | Return the authenticated user |
| POST | /auth/logout |
Yes | Invalidate the current JWT |
| POST | /auth/refresh |
Yes | Refresh the current JWT |
| Method | Endpoint | Auth | Admin | Description |
|---|---|---|---|---|
| GET | /students |
Yes | No | List students |
| POST | /students |
Yes | Yes | Create a student |
| GET | /students/{id} |
Yes | No | Show a student |
| PATCH | /students/{id} |
Yes | Yes | Update a student |
| DELETE | /students/{id} |
Yes | Yes | Delete a student |
Create student request:
{
"name": "John Doe",
"email": "john@example.com",
"birth_date": "2004-02-02",
"gender": "male",
"phone": "41999999999",
"address": "123 Main Street"
}Allowed gender values:
male, female
| Method | Endpoint | Auth | Admin | Description |
|---|---|---|---|---|
| GET | /professors |
Yes | No | List professors |
| POST | /professors |
Yes | Yes | Create a professor |
| GET | /professors/{id} |
Yes | No | Show a professor |
| PATCH | /professors/{id} |
Yes | Yes | Update a professor |
| DELETE | /professors/{id} |
Yes | Yes | Delete a professor |
| GET | /professors/{id}/courses |
Yes | No | List courses assigned to a professor |
Create professor request:
{
"name": "Jane Smith",
"email": "jane@example.com",
"cpf": "52998224725",
"birth_date": "1985-06-15",
"gender": "female",
"phone": "41999999999",
"address": "456 College Avenue"
}Allowed gender values:
male, female
| Method | Endpoint | Auth | Admin | Description |
|---|---|---|---|---|
| GET | /courses |
Yes | No | List courses |
| POST | /courses |
Yes | Yes | Create a course |
| GET | /courses/{id} |
Yes | No | Show a course |
| PATCH | /courses/{id} |
Yes | Yes | Update a course |
| DELETE | /courses/{id} |
Yes | Yes | Delete a course |
| POST | /courses/{id}/students |
Yes | Yes | Enroll a student in a course |
| DELETE | /courses/{courseId}/students/{studentId} |
Yes | Yes | Unenroll a student from a course |
| GET | /courses/{id}/students |
Yes | No | List students enrolled in a course |
| POST | /courses/{id}/professor |
Yes | Yes | Assign a professor to a course |
| DELETE | /courses/{id}/professor |
Yes | Yes | Remove the assigned professor from a course |
Create course request:
{
"title": "Introduction to Computer Science",
"description": "Basic programming and computing concepts.",
"language": "en",
"level": "beginner",
"status": "active",
"start_date": "2026-02-01",
"end_date": "2026-06-30",
"professor_id": 1
}Allowed language values:
en, pt-br, fr, other
Allowed level values:
beginner, intermediate, advanced
Allowed status values:
active, inactive
Enroll a student:
POST /api/courses/1/students{
"student_id": 1
}Assign a professor:
POST /api/courses/1/professor{
"professor_id": 1
}List endpoints return Laravel paginated responses.
The page size is controlled by:
PAGINATION_SIZE=20Use the page query parameter to navigate:
GET /api/students?page=2Common responses:
| Status | Meaning |
|---|---|
| 200 | Request completed successfully |
| 201 | Resource created or enrollment completed |
| 204 | Resource deleted or relationship removed |
| 401 | Unauthenticated or authenticated user is not an admin |
| 404 | Requested entity was not found |
| 409 | Duplicate email, CPF, or existing enrollment conflict |
| 422 | Validation error |
Validation errors return:
{
"errors": {
"field": [
"Validation message"
]
}
}Domain errors return:
{
"error": {
"message": "Error message"
}
}Run the test suite:
php artisan testOr use the Composer script:
composer testWith Sail:
./vendor/bin/sail artisan testLaravel Pint is installed for code formatting:
./vendor/bin/pintapp/
Enums/ Domain enum values
Exceptions/ Domain-specific exceptions
Helpers/ Shared helper utilities and exception response handling
Http/Controllers/ API controllers
Models/ Eloquent models
Repositories/ Persistence layer
Services/ Business logic layer
config/
auth.php JWT guard configuration
jwt.php JWT configuration
scramble.php OpenAPI documentation configuration
database/
migrations/ Database schema
factories/ Test and seed factories
seeders/ Database seeders
routes/
api.php API routes
tests/
Feature/ Feature tests
Unit/ Unit tests
- All protected routes require
Authorization: Bearer <token>. - Read endpoints require authentication but do not require admin access.
- Write and relationship-management endpoints require admin access.
birth_date,start_date, andend_dateuseYYYY-MM-DDformat in request payloads.- A course can have many students.
- A course can have one professor.
- A professor can be assigned to many courses.