Skip to content

About

A simple project management application for organizing projects, tasks, and comments.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

TaskFlow

TaskFlow is a small educational project-management application built to practice and demonstrate modern Laravel, Vue, and Nuxt development.

The project focuses on clean, understandable fundamentals such as authentication, API development, Eloquent relationships, validation, authorization, testing, and frontend API integration.

Features

  • Multi-method API authentication
  • Laravel Sanctum API authentication
  • JWT API authentication
  • Laravel Passport OAuth2 authentication
  • Token-based API authentication
  • Logout and authentication state handling
  • User-owned Projects
  • User-owned Tasks
  • Projects containing Tasks
  • Comments on Tasks
  • Task CRUD
  • Project CRUD
  • Task status and priority filtering
  • Pagination
  • Form Request validation
  • API Resources
  • Policies and resource authorization
  • Protection against assigning Tasks to another user's Project
  • JSON API error handling for common HTTP errors
  • Frontend validation error display
  • Nuxt/Vue task management UI
  • Nuxt/Vue project management UI
  • Basic responsive styling
  • Interactive API documentation (Scramble + Scalar)
  • Laravel feature tests
  • Docker-based development environment

Tech Stack

Backend

  • PHP 8.5
  • Laravel 13
  • Laravel Sanctum
  • tymon/jwt-auth
  • Laravel Passport
  • Scramble (API documentation)
  • Eloquent ORM
  • SQLite for local development and automated tests
  • MySQL-compatible configuration for environments using MySQL

Frontend

  • Nuxt
  • Vue
  • TypeScript
  • Vite

Infrastructure

  • Docker
  • Docker Compose
  • Nginx
  • PHP-FPM
  • Node.js

Application Architecture

The application is split into a Laravel backend and a Nuxt frontend.

Browser
   |
   v
Nginx
   |
   +--------------------+
   |                    |
   v                    v
Nuxt / Vue          Laravel API
                        |
                        v
                    Eloquent
                        |
                        v
                  SQLite / MySQL

Nginx exposes a single application entry point:

/api/*   -> Laravel
/*       -> Nuxt

The frontend does not expose its development port directly to the host.

Domain Model

The current domain relationships are:

User
├── hasMany Projects
├── hasMany Tasks
└── hasMany Comments

Project
├── belongsTo User
└── hasMany Tasks

Task
├── belongsTo User
├── belongsTo Project
└── hasMany Comments

Comment
├── belongsTo User
└── belongsTo Task

Conceptually:

User
 ├── Project A
 │    ├── Task 1
 │    │    ├── Comment
 │    │    └── Comment
 │    └── Task 2
 │
 └── Project B
      └── Task 3

Requirements

You need:

  • Docker
  • Docker Compose
  • Git

No PHP, Composer, Node.js, or Nginx installation is required on the host because they run inside Docker containers.

Installation

Clone the repository and enter the project directory.

Copy the environment file:

cp .env.example .env

Build and start the application:

./bin/run.sh build

Generate the Laravel application key:

./bin/artisan key:generate

Generate JWT_SECRET (only once if JWT_SECRET is empty):

./bin/artisan jwt:secret

Generate passport keys (only once if they don't exist in backend/storage):

./bin/artisan passport:keys

Database directory ownership is handled automatically — bin/run.sh maps your host user/group into the containers, so the SQLite file created by migrate is owned by your user.

Run migrations:

./bin/artisan migrate --seed

The application is then available through the configured host.

The development setup currently uses:

http://localhost:8080

If this hostname is not configured on your machine, point it to 127.0.0.1 or adjust the frontend/Vite host configuration accordingly.

Docker Services

The project contains three application services.

Backend

Laravel + PHP-FPM.

taskflow-backend

The container runs PHP-FPM on port 9000.

Frontend

Nuxt development server.

taskflow-frontend

The Nuxt server listens internally on port 3000.

Nginx

Single public entry point.

taskflow-nginx

Nginx exposes the application through the configured APP_PORT, which defaults to 8080.

There is intentionally no database container in the current development environment.

Environment

The root .env file is injected into the backend container as environment variables (via Compose env_file).

Example development configuration:

APP_NAME=TaskFlow
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8080
APP_PORT=8080
DB_CONNECTION=sqlite
JWT_SECRET=

JWT authentication requires JWT_SECRET.

If the local .env does not already contain a JWT secret, generate one with:

./bin/artisan jwt:secret

The local .env file is not committed to Git.

Use .env.example as the committed environment template.

Application Port

APP_PORT controls the port exposed by the Nginx container on the host.

The default is:

APP_PORT=8080

MySQL

The application can be configured to use MySQL instead of SQLite:

DB_CONNECTION=mysql
DB_HOST=dbhost
DB_PORT=3306
DB_DATABASE=taskflow.app
DB_USERNAME=tester
DB_PASSWORD=tester

The current Docker setup does not include a MySQL container because the development environment uses SQLite.

Database

SQLite is used for local development and automated tests because it keeps the project setup lightweight.

The SQLite database is located at:

backend/database/database.sqlite

Laravel migrations are used to create and evolve the database schema.

Run migrations with:

./bin/artisan migrate

Reset and seed the database with:

./bin/artisan migrate:fresh --seed

Artisan Helper

The repository contains a convenience wrapper:

bin/artisan

This allows Laravel Artisan commands to be executed without entering the backend container manually.

Examples:

./bin/artisan --version
./bin/artisan migrate
./bin/artisan migrate:fresh --seed
./bin/artisan route:list
./bin/artisan tinker
./bin/artisan test

Laravel generator commands can also be run through the wrapper:

./bin/artisan make:model Project -m
./bin/artisan make:controller Api/ProjectController
./bin/artisan make:request StoreProjectRequest

Generated files are handled on the host so that newly created project files are owned by the user running the command rather than by the Docker container user.

Authentication

TaskFlow demonstrates three authentication mechanisms:

  • Sanctum — Laravel personal access tokens
  • JWT — tymon/jwt-auth
  • Passport — OAuth2 authorization server

Protected API routes use the auth.multi middleware. The authentication method is selected with the X-Auth-Method request header.

Supported values:

  • sanctum
  • jwt
  • passport

If X-Auth-Method is omitted, Sanctum is used by default.

Example:

GET /api/v1/tasks
Authorization: Bearer <token>
X-Auth-Method: jwt

Passport setup

Passport uses OAuth2 keys stored in the backend storage directory:

backend/storage/oauth-private.key
backend/storage/oauth-public.key

These keys are environment-specific and should not be committed to the repository.

Passport OAuth clients are stored in the application's oauth_clients table.

For manual API testing with the password grant, create a password-grant OAuth client using the Passport Artisan command and keep its client ID and secret available for your requests.

The OAuth token endpoint is:

POST /api/v1/oauth/token

The endpoint accepts OAuth2 parameters such as:

{
  "grant_type": "password",
  "client_id": "<client-id>",
  "client_secret": "<client-secret>",
  "username": "john@example.com",
  "password": "password123",
  "scope": ""
}

The returned access_token can then be used with:

Authorization: Bearer <access-token>
X-Auth-Method: passport

The project tests do not depend on a manually created OAuth client. Passport clients required by tests are created inside the test database.

Register

POST /api/v1/register

Example request:

{
  "name": "John Doe",
  "email": "john@example.com",
  "password": "password123",
  "password_confirmation": "password123"
}

Login

POST /api/v1/login

Example request:

{
  "email": "john@example.com",
  "password": "password123"
}

The API returns a Sanctum bearer token.

JWT login uses the dedicated endpoint:

POST /api/v1/login/jwt

Example request:

{
  "email": "john@example.com",
  "password": "password123"
}

The API returns a JWT bearer token.

For Passport login, the frontend first requests the password-grant OAuth client:

GET /api/v1/oauth/client

and then exchanges the credentials at POST /api/v1/oauth/token.

Authenticated requests use:

Authorization: Bearer <token>

Current user

GET /api/v1/user

Requires authentication.

Logout

POST /api/v1/logout

Requires authentication. The behavior depends on the authentication method sent via X-Auth-Method:

  • sanctum — deletes the current Sanctum access token
  • jwt — invalidates the current JWT
  • passport — revokes the current Passport access token

API

All protected API routes require a valid bearer token authenticated through the selected authentication method.

The TaskFlow API is versioned with the v1 path prefix — every endpoint, including the Passport OAuth2 server and its helper endpoints, lives under /api/v1/*.

Tasks

GET    /api/v1/tasks
POST   /api/v1/tasks
GET    /api/v1/tasks/{task}
PUT    /api/v1/tasks/{task}
DELETE /api/v1/tasks/{task}

The task list supports filtering:

GET /api/v1/tasks?status=completed
GET /api/v1/tasks?priority=high
GET /api/v1/tasks?status=completed&priority=high

Pagination is provided by Laravel's paginator.

Example:

GET /api/v1/tasks?page=2

Task creation

A Task belongs to a Project.

Example:

{
  "project_id": 1,
  "title": "Build login page",
  "description": "Implement the login screen",
  "status": "pending",
  "priority": "high",
  "due_date": "2026-09-10"
}

The API verifies that the selected Project belongs to the authenticated user.

Clients cannot set user_id to control task ownership.

Projects

GET    /api/v1/projects
POST   /api/v1/projects
GET    /api/v1/projects/{project}
PUT    /api/v1/projects/{project}
DELETE /api/v1/projects/{project}

Projects are owned by their creating user.

Project creation

Example:

{
  "name": "Website Redesign",
  "description": "Redesign the company website"
}

Comments

Comments are associated with Tasks.

GET    /api/v1/tasks/{task}/comments
POST   /api/v1/tasks/{task}/comments
GET    /api/v1/tasks/{task}/comments/{comment}
PUT    /api/v1/tasks/{task}/comments/{comment}
DELETE /api/v1/tasks/{task}/comments/{comment}

Example:

{
  "body": "Looks good to me."
}

A user can only access comments through Tasks they are authorized to view.

API Documentation

The backend ships with interactive API documentation generated by Scramble and rendered with Scalar.

The interactive documentation is available at:

GET /api/docs

The raw OpenAPI 3.1 specification is served at:

GET /api/docs.openapi.json

The specification describes the full API:

  • every endpoint is versioned under v1 — the server is http://localhost:8080 and operation paths keep the full /api/v1 prefix (e.g. POST /api/v1/register, POST /api/v1/oauth/token)
  • endpoints grouped into Authentication, OAuth2, Tasks, Projects, and Comments
  • both authentication security schemes

Authenticated endpoints require two security schemes:

  • Authorization: Bearer <token> — HTTP bearer token (Sanctum, JWT, or Passport)
  • X-Auth-Method: sanctum|jwt|passport — header selecting the authentication strategy; documented as a companion header, not an API key

The Passport token flow (POST /api/v1/oauth/token and /api/v1/oauth/token/refresh) is grouped under OAuth2. The unused authorization-code and device flows are excluded from the generated specification.

Endpoint sections are driven by controller PHPDoc summaries and the #[Group] attribute.

Validation

Laravel Form Requests are used for request validation.

Examples include:

  • required fields
  • string validation
  • maximum lengths
  • allowed status values
  • allowed priority values
  • date validation
  • email validation
  • unique email validation
  • existing Project validation
  • Project ownership validation

Validation failures return HTTP 422.

The application currently uses Polish Laravel validation messages in:

backend/lang/pl/validation.php

The Nuxt frontend displays field-specific validation errors returned by the API.

Authorization

Authorization is handled using Laravel Policies.

TaskPolicy

Users can view, update, or delete Tasks only when they own the Task.

ProjectPolicy

Users can view, update, or delete Projects only when they own the Project.

Project ownership during Task creation/update

A particularly important authorization rule is that users cannot associate Tasks with Projects belonging to another user.

User A
 ├── Project A
 └── Task A

User B
 └── Project B

User A cannot create or move a Task to Project B.

This ownership rule is enforced on the backend rather than relying on frontend behavior.

API Error Handling

The API returns JSON responses for common errors.

Unauthenticated

401
{
  "success": false,
  "message": "Unauthenticated."
}

Unauthorized

403
{
  "success": false,
  "message": "This action is unauthorized."
}

Not Found

404
{
  "success": false,
  "message": "Resource not found."
}

This keeps the API behavior predictable for the Nuxt frontend.

Frontend

The Nuxt application contains separate pages for authentication, Tasks, and Projects.

frontend/app/pages/login.vue
frontend/app/pages/tasks.vue
frontend/app/pages/projects.vue

Shared navigation is implemented as:

frontend/app/components/AppNav.vue

Shared Task-related TypeScript definitions are stored in:

frontend/app/types/task.ts

Global styling is stored in:

frontend/app/assets/css/main.css

Authentication state

The frontend stores the bearer token and the selected authentication method in browser localStorage.

The authentication composable is:

frontend/app/composables/useAuth.ts

It handles:

  • storing the token and selected method
  • retrieving the token and selected method
  • removing the token and selected method
  • logout

The login page (frontend/app/pages/login.vue) lets the user pick the authentication method (Sanctum, JWT, or Passport).

API requests centralize their headers through:

frontend/app/composables/useApi.ts

which sets Accept, X-Auth-Method, and the Authorization bearer header on every request.

A client plugin (frontend/app/plugins/auth-header.ts) restores the stored authentication method so subsequent requests keep using the same method.

Browser-only APIs are guarded so they are not accessed during Nuxt server-side rendering.

Tasks UI

The Tasks page currently supports:

  • loading authenticated Tasks
  • creating Tasks
  • editing Tasks
  • deleting Tasks
  • selecting a Project
  • filtering by status
  • filtering by priority
  • pagination
  • loading comments
  • adding comments
  • displaying validation errors
  • basic authentication redirects

Task actions are separated from task information visually, with editing/deletion controls placed on the right side of a task row and comments kept with the task content.

Projects UI

The Projects page currently supports:

  • loading Projects
  • creating Projects
  • editing Projects
  • deleting Projects
  • displaying validation errors
  • navigation to Tasks
  • logout

Styling

The frontend uses a deliberately minimal global CSS layer.

The styling focuses on:

  • readable typography
  • centered content
  • full-width application navigation
  • consistent forms
  • consistent buttons
  • simple cards/rows
  • basic responsive behavior
  • clear validation/error messages

No external component library is required.

Testing

Laravel feature tests cover the main API behavior.

Everything runs inside the Docker containers (PHP, Composer, Node, and the pinned package/tool versions are only available there — nothing needs to be installed on the host). Start the stack first if it isn't running:

./bin/run.sh start

Run the complete test suite with:

./bin/artisan test

./bin/artisan is just a wrapper for docker compose exec backend php artisan <args>, so the same command works by hand:

docker compose exec backend php artisan test

The tests use an in-memory SQLite database (DB_DATABASE=:memory:), so no migrations or database setup are required before running them.

Run a specific test class or method:

./bin/artisan test --filter=TaskApiTest
./bin/artisan test --filter=ProjectApiTest
./bin/artisan test --filter=CommentApiTest
./bin/artisan test --filter=MultiAuthTest
./bin/artisan test --filter=test_logout_invalidates_jwt_token

Or run a single test file:

./bin/artisan test tests/Feature/MultiAuthTest.php

Check backend code style with Laravel Pint (inside the container):

docker compose exec backend vendor/bin/pint --test   # check only
docker compose exec backend vendor/bin/pint          # fix files

The frontend has no automated test suite; validate it with a production build:

docker compose exec frontend npm run build

CI

GitHub Actions runs two workflows on pushes and pull requests that touch backend/**:

  • Lint — runs Laravel Pint in check mode (pint --test)
  • Test — runs PHPUnit with pcov coverage and enforces a minimum 62% coverage threshold

Both workflows use PHP 8.5 and run on ubuntu-latest.

Current test coverage includes:

Tasks

  • listing a user's Tasks
  • creating Tasks
  • viewing Tasks
  • updating Tasks
  • deleting Tasks
  • authorization between users
  • validation
  • authentication
  • Project ownership during Task creation
  • Project ownership during Task updates

Projects

  • listing Projects
  • creating Projects
  • viewing Projects
  • updating Projects
  • deleting Projects
  • authorization between users
  • validation

Comments

  • listing comments
  • creating comments
  • task ownership authorization
  • validation
  • unauthenticated access

Multi-authentication

MultiAuthTest verifies authentication through all three supported mechanisms:

  • Sanctum authentication with an explicit X-Auth-Method: sanctum header
  • JWT authentication with an explicit X-Auth-Method: jwt header
  • Passport authentication with an explicit X-Auth-Method: passport header
  • Sanctum as the default when X-Auth-Method is not provided
  • 401 Unauthorized for an invalid X-Auth-Method
  • logout deletes the current Sanctum token
  • logout invalidates the current JWT
  • logout revokes the current Passport token

The Passport test creates its OAuth client and obtains an access token through the OAuth2 password grant, keeping the test independent of manually configured OAuth clients.

Tests use Laravel's RefreshDatabase and Sanctum's actingAs() helpers.

Project Structure

Relevant repository structure:

.
├── .github/
│   └── workflows/
│       ├── lint.yaml                     # GitHub Actions — Laravel Pint
│       └── test.yaml                     # GitHub Actions — PHPUnit + coverage
├── backend/                        # Laravel API
│   ├── app/
│   │   ├── Http/
│   │   │   ├── Controllers/Api/
│   │   │   ├── Middleware/
│   │   │   ├── Requests/
│   │   │   └── Resources/
│   │   ├── Models/
│   │   └── Policies/
│   ├── config/
│   ├── database/                   # migrations + database.sqlite
│   ├── lang/                       # en, pl validation messages
│   ├── routes/api.php
│   └── tests/Feature/
│
├── frontend/                       # Nuxt / Vue
│   └── app/
│       ├── assets/css/
│       ├── components/
│       ├── composables/
│       ├── middleware/
│       ├── pages/
│       ├── plugins/
│       └── types/
│
├── bin/
│   ├── artisan                     # docker compose wrapper for artisan
│   └── run.sh                      # docker compose wrapper
│
└── docker/
    ├── docker-compose.yml
    ├── configs/nginx/default.conf
    ├── image-backend/Dockerfile
    └── image-frontend/Dockerfile

All Docker-related configuration is kept under the docker/ directory.

Design Decisions

Keep ownership on the server

Task and Project ownership is derived from the authenticated user.

The frontend cannot choose a different user_id to transfer ownership.

Use Policies for resource authorization

Task and Project authorization rules are implemented with Laravel Policies rather than duplicated throughout controllers.

This keeps ownership rules centralized and reusable.

Use Form Requests for validation

Request validation is kept out of controllers where practical.

This makes controllers easier to read and keeps validation rules close to the request being validated.

Use API Resources

Laravel API Resources provide a consistent response structure and prevent exposing raw Eloquent models directly as the public API contract.

Use relationship-based ownership checks

When assigning a Task to a Project, the Project is looked up through the authenticated user's Projects rather than simply checking that the Project ID exists.

This prevents a user from attaching data to another user's resources.

Use SQLite for development and tests

SQLite keeps local development and automated testing simple and fast.

The application is also configured so it can use MySQL when required.

Avoid unnecessary abstractions

The project deliberately does not introduce Repository or Service layers everywhere.

Abstractions should solve a real problem rather than exist only for structural purposes.

Transactions

Database transactions are not used simply for demonstration purposes.

A transaction is appropriate when a single application operation performs multiple related writes that must succeed or fail together. The current CRUD operations do not require an artificial transaction.

SQLite supports transactions, so the development database does not prevent using them when a real use case appears.

Development Commands

Control the Docker application with bin/run.sh:

./bin/run.sh        # start
./bin/run.sh build  # rebuild
./bin/run.sh stop   # stop

The Artisan Helper section above documents migration, route, test, and shell commands.

Future Improvements

Possible next improvements include:

  • Role-based access control
  • Project member management
  • More granular permissions
  • Comment editing and deletion
  • More comprehensive frontend tests
  • Improved production deployment configuration
  • Better confirmation flows for destructive actions
  • More reusable frontend components

These are intentionally outside the scope of the current project.

Learning Goals

This project is intended as a practical learning exercise for:

  • Laravel API development
  • Eloquent relationships
  • Form Requests
  • Policies and authorization
  • API Resources
  • Sanctum authentication
  • Pagination and filtering
  • Validation
  • Feature testing
  • Vue/Nuxt application development
  • TypeScript
  • API integration
  • Docker-based development
  • Frontend state management
  • Separation between frontend and backend responsibilities

The implementation intentionally favors straightforward Laravel and Vue patterns that are easy to understand, test, and maintain.

License

This project was created as an educational project for learning Laravel, Vue, and Nuxt.

About

A simple project management application for organizing projects, tasks, and comments.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages