{"id":30694234,"url":"https://github.com/phalcon/rest-api-v6","last_synced_at":"2026-03-15T22:48:02.255Z","repository":{"id":311846299,"uuid":"1045076984","full_name":"phalcon/rest-api-v6","owner":"phalcon","description":"A REST API developed with Phalcon v6","archived":false,"fork":false,"pushed_at":"2025-08-27T00:34:07.000Z","size":98,"stargazers_count":0,"open_issues_count":3,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-08-27T09:18:27.918Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phalcon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"phalcon","open_collective":"phalcon"}},"created_at":"2025-08-26T16:09:25.000Z","updated_at":"2025-08-26T16:09:29.000Z","dependencies_parsed_at":"2025-08-27T09:18:32.332Z","dependency_job_id":"8abe146d-4a6b-4083-b861-a1da08dca56b","html_url":"https://github.com/phalcon/rest-api-v6","commit_stats":null,"previous_names":["phalcon/rest-api-v6"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/phalcon/rest-api-v6","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phalcon%2Frest-api-v6","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phalcon%2Frest-api-v6/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phalcon%2Frest-api-v6/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phalcon%2Frest-api-v6/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phalcon","download_url":"https://codeload.github.com/phalcon/rest-api-v6/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phalcon%2Frest-api-v6/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273238776,"owners_count":25069764,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-02T02:00:09.530Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-02T06:04:55.926Z","updated_at":"2025-12-28T00:51:10.524Z","avatar_url":"https://github.com/phalcon.png","language":null,"funding_links":["https://github.com/sponsors/phalcon","https://opencollective.com/phalcon"],"categories":[],"sub_categories":[],"readme":"# REST API with Phalcon v6\n\nA REST API developed with Phalcon v6. This document explains how the project is organised, how the main components interact, and the important design decisions to keep in mind when extending the codebase.\n\n## Introduction\n\nOur goal is to build a REST API that has:\n- Slim/efficient design\n- Middleware\n- JSON (or other) responses\n- Action/Domain/Responder implementation\n- JWT token authentication\n\n\u003e This is not **THE** way to build a REST API with Phalcon. It is simply **A** way to do that. You can adopt this implementation if you wish, parts of it or none of it.\n\nThis application has evolved significantly with every video release. Several areas were implemented in one way and later refactored to demonstrate the design trade-offs and how earlier choices affect the codebase.\n\nThe main takeaways that we want to convey to developers are:\n- The code has to be easy to read and understand\n- Each component must do one thing and one thing only\n- Components can be swapped out with others so the use of interfaces is essential\n- Static analysis tools (PHPStan) must not produce errors\n- Code coverage for tests must be at 100%\n                                                    \n### Videos on YouTube\n\n- Part 01 https://youtu.be/f3wP_M_NFKc\n- Part 02 https://youtu.be/VEZvUf_PdSY\n- Part 03 https://youtu.be/LP64Doh0t4g\n- Part 04 https://youtu.be/jCEZ2WMil8Q\n- Part 05 https://youtu.be/syU_3cIXFMM\n- Part 06 https://youtu.be/AgCbqW-leCM\n- Part 07 https://youtu.be/tGV4pSyVLdI\n- Part 08 https://youtu.be/GaJhNnw_1cE\n- Part 09 https://youtu.be/CWofDyTdToI\n- Part 10 https://youtu.be/8YUrGAbafaA\n\n## Directory Structure\n\nThe directory structure for this projects follows the recommendations of [pds/skeleton][pds_skeleton]\n\nThe folders contain:\n\n- `bin`: empty for now, we might use it later on\n- `config`: .env configuration files for CI and example\n- `docs`: documentation (TODO)\n- `public`: entry point of the application where `index.php` lives\n- `resources`: stores database migrations and docker files for local development\n- `src`: source code of the project\n- `storage`: various storage data such as application logs\n- `tests`: tests\n\n## High-level architecture\n\nThe application follows the [ADR pattern][adr_pattern] where the application is split into an `Action` layer, the `Domain` layer and a `Responder` layer.\n\n- `Action` — receives HTTP input, collects and sanitizes request data, and calls a Domain service.\n- `Domain` — contains the application logic. Implements small components, services that map to endpoints, validators, repositories and helpers.\n- `Responder` — builds and emits the HTTP response from a `Payload`.\n\nCore files live under `src/` and are registered in the DI container in `src/Domain/Components/Container.php`.\n\n## Main components\n\n### `Action` layer\n\nContains a handler that translate HTTP requests into Domain calls. For example, actions route requests to `LoginPostService`, `LogoutPostService`, `RefreshPostService` etc.\n\n### `Domain` layer\n\n#### `ADR`\n\n- `Payload`: A uniform result object used across Domain → Responder.\n- `Input`: Class collecting request input and used to pass it to the domain\n- Interfaces for domain and `Input`\n\n#### `Infrastructure`\n\n##### `Constants`\n\nClasses with constants and helper methods used throughout the application \n\n##### `DataSource`\n\n**`Auth`**\n\nContains Data Transfer Objects (DTOs) to move data from input to domain and from database back to domain. A Facade is available for orchestration, sanitizer for input as well as validators.\n\n**`Interfaces`**\n\nMapper and Sanitizer interfaces\n\n**`User`**\n\nContains Data Transfer Objects (DTOs) to move data from input to domain and from database back to domain. A Facade is available for orchestration, a repository for database operations, sanitizer for input as well as validators.\n\n**`Validation`**\n\nContains the `ValidatorInterface` for all validators, a `Result` object for returning back validation results/errors and the `AbsInt` validator to check the id for `Put` operations.\n\n##### `Encryption`\n\nContains components for JWT handling and passwords. The `Security` component is a wrapper for the `password_*` PHP classes, which are used for password hashing and verification.\n\nThe `TokenManager` offers methods to issue, refresh and revoke tokens. It works in conjunction with the `TokenCache` to store or invalidate tokens stored in Cache (Redis)\n\n##### `Enums`\n\nThere are several enumerations present in the application. Those help with common values for tasks. For example the `FlagsEnum` holds the values for the `co_users.usr_status_flag` field. We could certainly introduce a lookup table in the database for \"status\" and hold the values there, joining it to the `co_users` table with a lookup table. However, this will introduce an extra join in our query which will inevitable reduce performance. Since the `FlagsEnum` can keep the various statuses, we keep everything in code instead of the database. Thorough tests for enumerations ensure that if a change is made in the future, tests will fail, so that database integrity can be kept.\n\nThe `RoutesEnum` holds the various routes of the application. Every route is represented by a specific element in the enumeration and the relevant prefix/suffix are calculated for each endpoint. Also, each endpoint is mapped to a particular service, registered in the DI container, so that the action handler can invoke it when the route is matched.\n\nFinally, the `RoutesEnum` also holds the middleware array, which defines their execution and the \"hook\" they will execute in (before/after).\n\n##### `Env`\n\nThe environment manager and adapters. It reads environment variables using [DotEnv][dotenv] as the main adapter but can be extended if necessary.\n\n##### `Exceptions`\n\nException classes used in the application.\n\n##### `Container`\n\nThe application uses the `Phalcon\\Di\\Di` container with minimal components lazy loaded. Each non \"core\" component is also registered there (i.e. domain services, responder etc.) and all necessary dependencies are injected based on the service definitions.\n\nAdditionally there are two `Providers` that are also registered in the DI container for further functionality. The `ErrorHandlerProvider` which caters for the starting up/shut down of the application and error logging, and the very important `RoutesProvider` which handles registering all the routes that the application serves.\n\n#### `Services`:\n\nSeparated also in `User` and `Auth` it contains the classes that the action handler will invoke. The naming of these services shows what endpoint they are targeting and what HTTP method will invoke them. For example the `LoginPostService` will be a `POST` to the `/auth/login`.\n\n### `Responder`\n\nThe `JsonResponder` responder is responsible for constructing the response with the desired output, and emitting it back to the caller. For the moment we have only implemented a JSON response with a specified array as the payload to be sent back.\n\nThe responder receives the outcome of the Domain, by means of a `Payload` object. The object contains all the data necessary to inject in the response.\n\n#### Response payload\n\nThe application responds always with a specific JSON payload. The payload contains the following nodes:\n- `data` - contains any data that are returned back (can be empty)\n- `errors` - contains any errors occurred (can be empty)\n- `meta` - array of information regarding the payload\n    - `code` - the application code returned\n    - `hash` - a `sha1` hash of the `data`, `errors` and timestamp\n    - `message` - `success` or `error`\n    - `timestamp` - the time in UTC format\n\n\n## Request flow (example: login)\n\n1. Route matches and middleware runs (see Middleware section below).\n2. `Action` extracts request body and calls `LoginPostService-\u003ehandle($data)`.\n3. `LoginPostService` calls the `AuthFacade-\u003eauthenticate($input, $loginValidator)` (method injection).\n4. `AuthFacade`:\n    - Builds DTO via `AuthInput`.\n    - Calls the supplied validator (`AuthLoginValidator`) which returns a `Result`.\n    - On success, fetches user via repository and verifies credentials (`Security`).\n    - Issues tokens via `TokenManager`.\n    - Returns a `Payload::success(...)`.\n5. `Responder` builds JSON and returns HTTP response.\n\n## Validators\n\n- Specific validators exist for each potential input that needs to be validated\n- Method injection is used for validators: the `AuthFacade` does not require a single validator in its constructor. Instead, callers pass the appropriate validator to each method: login uses `AuthLoginValidator`, logout/refresh use `AuthTokenValidator`.\n- The validation `Result` supports `meta` data. Token validators may perform repository lookups and attach the resolved `User` to `ValidationResult-\u003emeta['user']` to avoid repeating DB queries. The facade reads that meta on success.\n\n## Token management and cache\n\n- `TokenManager` depends on a domain-specific `TokenCacheInterface` rather than a raw PSR cache. This keeps token-specific operations discoverable and testable.\n- `TokenCache` enhances the Cache operations by providing token specific operations for storing and invalidating tokens. \n- `TokenCacheInterface` defines token operations like `storeTokenInCache` and `invalidateForUser`.\n\n## Middleware sequence\n\nThere are several middleware registered for this application and they are being executed one after another (order matters) before the action is executed. As a result, the application will stop executing if an error occurs, or if certain validations fail. Middleware returns early with a `Payload` error when validation fails.\n\nThe middleware execution order is defined in the `RoutesEnum`. The available middleware is:\n\n- [NotFoundMiddleware.php](src/Domain/Infrastructure/Middleware/NotFoundMiddleware.php)\n- [HealthMiddleware.php](src/Domain/Infrastructure/Middleware/HealthMiddleware.php)\n- [ValidateTokenClaimsMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddleware.php)\n- [ValidateTokenPresenceMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php)\n- [ValidateTokenRevokedMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddleware.php)\n- [ValidateTokenStructureMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php)\n- [ValidateTokenUserMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php)\n\n**NotFoundMiddleware**\n\nChecks if the route has been matched. If not, it will return a `Resource Not Found` payload\n\n**HealthMiddleware**\n\nInvoked when the `/health` endpoint is called and returns a `OK` payload\n\n**ValidateTokenPresenceMiddleware**\n\nChecks if a JWT token is present in the `Authorization` header. If not, an error is returned\n\n**ValidateTokenStructureMiddleware**\n\nGets the JWT token and checks if it can be parsed. If not, an error is returned\n\n**ValidateTokenUserMiddleware**\n\nGets the userId from the JWT token, along with other information, and tries to match it with a user in the database. If the user is not found, an error is returned\n\n**ValidateTokenClaimsMiddleware**\n\nChecks all the claims of the JWT token to ensure that it validates. For instance, this checks the token validity (expired, not before), the claims, etc. If a validation error happens, then an error is returned.\n\n**ValidateTokenRevokedMiddleware**\n\nChecks if the token has been revoked. If it has, an error is returned\n\n\n[adr_pattern]: https://github.com/pmjones/adr\n[pds_skeleton]: https://github.com/php-pds/skeleton\n[dotenv]: https://github.com/vlucas/phpdotenv\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphalcon%2Frest-api-v6","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphalcon%2Frest-api-v6","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphalcon%2Frest-api-v6/lists"}