diff --git a/core/getting-started.md b/core/getting-started.md index b0034f0b68e..2d9e9c1342f 100644 --- a/core/getting-started.md +++ b/core/getting-started.md @@ -10,8 +10,9 @@ library manually. ### Symfony -If you are starting a new project, the easiest way to get API Platform up is to install -[API Platform for Symfony](../symfony/index.md). +If you are starting a new project, the easiest way to get API Platform up is to use the CLI: +`api-platform my-project --framework=symfony`. See [API Platform for Symfony](../symfony/index.md) +for details. It comes with the API Platform core library integrated with [the Symfony framework](https://symfony.com), [the schema generator](../schema-generator/index.md), diff --git a/laravel/index.md b/laravel/index.md index 3a9b238010e..573d312f758 100644 --- a/laravel/index.md +++ b/laravel/index.md @@ -87,6 +87,17 @@ cd my-api-platform-laravel-app ## Installing API Platform +> [!TIP] The API Platform CLI can automate all of the steps below. To scaffold a new Laravel project +> with API Platform already installed, run: +> +> ```console +> api-platform my-project --framework=laravel +> ``` +> +> This detects the `laravel` installer if available, creates the project, requires +> `api-platform/laravel`, and runs `php artisan api-platform:install` for you. The manual steps +> below remain valid for adding API Platform to an existing Laravel project. + In your Laravel project, install the API Platform integration for Laravel: ```console diff --git a/symfony/caddy.md b/symfony/caddy.md index 0c907a2cb0d..4ca5bab420a 100644 --- a/symfony/caddy.md +++ b/symfony/caddy.md @@ -1,16 +1,120 @@ # Configuring the Caddy Web Server with Symfony -[The API Platform Symfony variant](index.md), when generated with Docker, is shipped with -[the Caddy web server](https://caddyserver.com). The build contains the -[Mercure](../core/mercure.md) and the [Vulcain](https://vulcain.rocks) Caddy modules. +When you scaffold a project with the [API Platform CLI](index.md), the Symfony application is built +on top of [`symfony-docker`](https://github.com/dunglas/symfony-docker), which ships +[the Caddy web server](https://caddyserver.com) running [FrankenPHP](https://frankenphp.dev). The +build contains the [Mercure](../core/mercure.md) and the [Vulcain](https://vulcain.rocks) Caddy +modules. -Caddy is positioned in front of the web API and of the Progressive Web App (PWA). It routes requests -to either service depending on the value of the `Accept` HTTP header or the path of the request. +The Caddyfile lives at `api/frankenphp/Caddyfile`. -Using the same domain to serve the API and the PWA -[improves performance by preventing unnecessary CORS preflight requests and encourages embracing the REST principles](https://dunglas.fr/2022/01/preventing-cors-preflight-requests-using-content-negotiation/). +## How the CLI Serves the API and the PWA -## Why `route {}` Is Required +By default the API and the Progressive Web App (PWA) are served **separately**: + +- Caddy serves the **API** on `https://localhost`. +- When you scaffold with `--with-pwa`, the Next.js application runs **standalone** with `pnpm dev` + on `http://localhost:3000`. It calls the API cross-origin using the `NEXT_PUBLIC_API_ENTRYPOINT` + value written to `pwa/.env.local`, and the CLI installs and configures + [`nelmio/cors-bundle`](https://github.com/nelmio/NelmioCorsBundle) on the API so those + cross-origin requests are allowed. + +This keeps the two applications independent and requires no Caddy configuration. If you prefer to +serve both on the **same domain** through Caddy — which +[improves performance by preventing unnecessary CORS preflight requests and encourages embracing the REST principles](https://dunglas.fr/2022/01/preventing-cors-preflight-requests-using-content-negotiation/) +— see +[Serving the API and the PWA on the Same Domain](#serving-the-api-and-the-pwa-on-the-same-domain) +below. + +## The Shipped Caddyfile + +Out of the box, `api/frankenphp/Caddyfile` routes every request to the PHP application. The relevant +part of the site block looks like this: + +```caddy +{$SERVER_NAME:localhost} { + root /app/public + encode zstd br gzip + + mercure { + # ...Mercure hub configuration... + } + + vulcain + + # Extra directives injected by the CLI (see "The Link Header" below) + {$CADDY_SERVER_EXTRA_DIRECTIVES} + + @phpRoute { + not path /.well-known/mercure* + not file {path} + } + rewrite @phpRoute index.php + + @frontController path index.php + php @frontController { + worker { + file ./public/index.php + } + } + + file_server { + hide *.php + } +} +``` + +Any request that is not an existing static file and is not a Mercure subscription is rewritten to +`index.php` and handled by Symfony through the FrankenPHP worker. + +## The `Link` Header + +The CLI adds a Hydra + Mercure `Link` header to every response. Rather than editing the Caddyfile +directly, it injects the directive through the `CADDY_SERVER_EXTRA_DIRECTIVES` environment variable +in `api/compose.yaml`, inside a recipe block: + +```yaml +###> api-platform/api-platform ### +CADDY_SERVER_EXTRA_DIRECTIVES: + 'header ?Link `; rel="http://www.w3.org/ns/hydra/core#apiDocumentation", + ; rel="mercure"`' +###< api-platform/api-platform ### +``` + +The `?` prefix means the header is only set when not already present in the response — a PHP +response that sets its own `Link` header is not overwritten. + +Setting it at the Caddy level serves two purposes: + +1. **API discoverability**: every response advertises the Hydra API documentation URL, allowing + clients to auto-discover the API. +2. **Mercure subscription**: every response advertises the Mercure hub URL, so clients can subscribe + to real-time updates without any application code. + +## Serving the API and the PWA on the Same Domain + +If you want Caddy to serve both the API and the Next.js application on a single domain, you need to +forward HTML requests to the PWA and keep API requests on PHP. This is **not** configured by the +CLI; the steps below add it on top of a scaffolded project. + +### 1. Make the PWA reachable from the Caddy container + +Caddy runs inside the `php` container, so it must be able to reach the Next.js server. Either: + +- run the PWA in a Docker service named `pwa` listening on port `3000` (then the upstream is + `pwa:3000`), or +- keep running `pnpm dev` on the host and target it with `host.docker.internal:3000`. + +Declare the upstream as an environment variable for the `php` service in `api/compose.yaml`: + +```yaml +services: + php: + environment: + PWA_UPSTREAM: pwa:3000 +``` + +### 2. Wrap the routing directives in a `route {}` block Caddy processes directives in a [predefined global order](https://caddyserver.com/docs/caddyfile/directives#directive-order), not in @@ -21,7 +125,8 @@ Next.js. Wrapping the directives in a `route {}` block enforces **strict first-match-wins evaluation in file order**. The first directive that matches a request wins, and Caddy stops evaluating the rest. This -is what makes the `@pwa` proxy check run before the PHP rewrite: +makes the `@pwa` proxy check run before the PHP rewrite. Replace the `@phpRoute … file_server` +section of the site block with: ```caddy route { @@ -34,16 +139,20 @@ route { # 3. Run PHP for index.php @frontController path index.php - php @frontController + php @frontController { + worker { + file ./public/index.php + } + } # 4. Serve remaining static files file_server { hide *.php } } ``` -## The `@pwa` Matcher +### 3. Define the `@pwa` matcher -The `@pwa` named matcher is a +Add a `@pwa` named matcher — a [CEL (Common Expression Language) expression](https://caddyserver.com/docs/caddyfile/matchers#expression) that decides which requests are forwarded to the Next.js application: @@ -62,7 +171,7 @@ that decides which requests are forwarded to the Next.js application: The expression has three independent clauses joined by `||`. A request matches `@pwa` if **any** clause is true. -### Clause 1: HTML requests that are not API paths +#### Clause 1: HTML requests that are not API paths A browser navigating to any URL sends `Accept: text/html, */*`. This clause forwards those requests to Next.js unless the path is known to be served by the API or carries an extension that API @@ -79,7 +188,7 @@ Paths excluded from Next.js (handled by PHP instead): | `/_profiler*`, `/_wdt*` | Symfony Web Debug Toolbar and Profiler | | `*.json*`, `*.html`, `*.csv`, `*.yml`, `*.yaml`, `*.xml` | Content-negotiated formats served by the API | -### Clause 2: Next.js static assets and well-known files +#### Clause 2: Next.js static assets and well-known files ```caddy path('/favicon.ico', '/manifest.json', '/robots.txt', '/sitemap*', '/_next*', '/__next*') @@ -89,7 +198,7 @@ These paths are forwarded to Next.js unconditionally, regardless of the `Accept` and `/__next/*` are the internal asset paths used by the Next.js runtime for JavaScript chunks, CSS, images, and hot module replacement updates in development. -### Clause 3: React Server Components requests +#### Clause 3: React Server Components requests ```caddy query({'_rsc': '*'}) @@ -100,57 +209,15 @@ Next.js uses the `_rsc` query parameter internally for data fetching. These requests do not carry `text/html` in their `Accept` header, so they would miss clause 1 without this dedicated check. -## The `Link` Header - -```caddy -header ?Link `; rel="http://www.w3.org/ns/hydra/core#apiDocumentation", ; rel="mercure"` -``` - -This directive is placed at the **site block level**, outside the `route {}` block, so it applies to -every response regardless of whether it came from PHP or Next.js. The `?` prefix means the header is -only set when not already present in the response — a PHP response that sets its own `Link` header -is not overwritten. - -Setting this at the Caddy level serves two purposes: - -1. **API discoverability**: every response advertises the Hydra API documentation URL, allowing - clients to auto-discover the API. -2. **Mercure subscription**: every response advertises the Mercure hub URL, so clients can subscribe - to real-time updates without any application code. - -The Next.js application does not need to set these headers itself — they arrive on every response -automatically. - -## The `PWA_UPSTREAM` Environment Variable - -```caddy -reverse_proxy @pwa http://{$PWA_UPSTREAM} -``` - -`PWA_UPSTREAM` is resolved at runtime from the container environment. In `compose.yaml` it is set to -`pwa:3000`, where `pwa` is the Docker Compose service name and `3000` is the default port of the -Next.js server. - -When the `pwa` service is not running (for example in an API-only project), Caddy returns a -`502 Bad Gateway` for any request matching `@pwa`. To run without a Next.js frontend, comment out -that line in the Caddyfile: - -```caddy -route { - # Comment the following line if you don't want Next.js to catch requests for HTML documents. - # In this case, they will be handled by the PHP app. - # reverse_proxy @pwa http://{$PWA_UPSTREAM} - - @phpRoute { not path /.well-known/mercure*; not file {path} } - rewrite @phpRoute index.php - @frontController path index.php - php @frontController - file_server { hide *.php } -} -``` +When the PWA upstream is unreachable, Caddy returns a `502 Bad Gateway` for any request matching +`@pwa`. To temporarily fall back to PHP-rendered HTML, comment out the `reverse_proxy @pwa` line +inside the `route {}` block. ## Adjusting the Routing Rules +The rules below assume you have enabled single-domain serving and therefore have a `@pwa` matcher to +tweak. + ### Routing an admin path to PHP If you use EasyAdmin, SonataAdmin, or a custom Symfony controller that serves HTML pages, add the diff --git a/symfony/index.md b/symfony/index.md index 760c63f7479..d308fe04372 100644 --- a/symfony/index.md +++ b/symfony/index.md @@ -126,6 +126,10 @@ When the PWA and the admin are enabled, the installer creates a project director `api/` subdirectory (the Symfony API) alongside a `pwa/` directory (the Next.js application) and an `admin/` directory (the React-admin SPA). The rest of this tutorial assumes this layout. +By default the installer also writes `AGENTS.md` and `CLAUDE.md` instruction files so AI coding +agents (Claude Code, Cursor, GitHub Copilot, …) know how to work with API Platform in your project. +Pass `--no-with-agents` to skip them. + API Platform is shipped with a [Docker](https://docker.com) definition that makes it easy to get a containerized development environment up and running. If you do not already have Docker on your computer, it's the right time to [install it](https://docs.docker.com/get-docker/). @@ -167,7 +171,9 @@ This starts the following services: When generated with `--with-pwa`, the Next.js application lives in the sibling `pwa/` directory. It is **not** part of the Docker Compose stack: you run it separately with its own development server -(see [A Next.js Web App](#a-nextjs-web-app) below). +(see [A Next.js Web App](#a-nextjs-web-app) below). To serve the API and the PWA on the same domain +through Caddy instead, see +[Configuring the Caddy Web Server](caddy.md#serving-the-api-and-the-pwa-on-the-same-domain). The following components are available: