Skip to content

tuchanski/simple-university-api

Repository files navigation

Simple University API

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.

Features

  • 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

Tech Stack

  • 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

Architectural Decisions

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

Why Layered Architecture

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

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

Services contain the core business logic of the application.

Examples:

  • StudentServiceImpl validates email uniqueness and gender before creating or updating students.
  • ProfessorServiceImpl validates email uniqueness, CPF uniqueness, CPF format, and professor data.
  • CourseServiceImpl handles 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

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.

Domain Exceptions

The project uses custom exceptions for domain errors, such as:

  • StudentNotFoundException
  • CourseNotFoundException
  • EmailAlreadyRegisteredException
  • StudentAlreadyEnrolledException
  • ProfessorAlreadyEnrolledException

These exceptions make failure cases explicit and allow the API to return consistent error responses through GlobalExceptionHandler.

Enums

The project uses PHP enums for fixed domain values:

  • Gender
  • Language
  • Level
  • Status

Enums avoid duplicated string values across the codebase and make accepted values easier to understand and maintain.

Authentication And Authorization

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.

Requirements

For a local setup:

  • PHP 8.2 or newer
  • Composer
  • Node.js and npm
  • MySQL

For a Docker setup:

  • Docker
  • Docker Compose

Environment

Create your environment file from the example:

cp .env.example .env

Important 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:secret

Installation

Install PHP and JavaScript dependencies:

composer install
npm install

Create the local MySQL database if it does not exist yet:

CREATE DATABASE simple_university_api;

Run the database migrations:

php artisan migrate

Optional: run the database seeder:

php artisan db:seed

Build frontend assets:

npm run build

Running The Application

Start the Laravel development server:

php artisan serve

The 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 dev

This starts the Laravel server, queue listener, logs, and Vite.

Running With Laravel Sail

Install dependencies first if they are not available:

composer install

Start the Docker containers:

./vendor/bin/sail up -d

Run migrations:

./vendor/bin/sail artisan migrate

Generate the keys if needed:

./vendor/bin/sail artisan key:generate
./vendor/bin/sail artisan jwt:secret

The API will be available at:

http://localhost/api

Authentication

This API uses JWT bearer tokens.

Register a user:

POST /api/auth/register

Request body:

{
  "name": "Admin User",
  "email": "admin@example.com",
  "password": "password123",
  "password_confirmation": "password123"
}

Login:

POST /api/auth/login

Request 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-token

Admin Access

Creating, 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 tinker

API Documentation

Scramble 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

API Endpoints

All endpoints below use the /api prefix.

Auth

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

Students

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

Professors

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

Courses

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
}

Pagination

List endpoints return Laravel paginated responses.

The page size is controlled by:

PAGINATION_SIZE=20

Use the page query parameter to navigate:

GET /api/students?page=2

Status Codes

Common 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"
  }
}

Tests

Run the test suite:

php artisan test

Or use the Composer script:

composer test

With Sail:

./vendor/bin/sail artisan test

Code Style

Laravel Pint is installed for code formatting:

./vendor/bin/pint

Project Structure

app/
  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

Notes

  • 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, and end_date use YYYY-MM-DD format in request payloads.
  • A course can have many students.
  • A course can have one professor.
  • A professor can be assigned to many courses.

About

REST API for managing an academic environment

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages