Skip to content

Commit 496b2f8

Browse files
committed
docs(symfony): document the API Platform CLI installer
Rework the Symfony getting-started and Caddy guides around the new `api-platform` CLI (api-platform/installer) instead of the legacy distribution. - symfony/index.md: install the CLI as a static binary or via `composer global require api-platform/installer`; scaffold with `api-platform <name> --framework=symfony` (there is no `new` subcommand). Document the standalone Next.js PWA model (runs on :3000, talks to the API cross-origin via CORS) instead of a containerized single-domain PWA. Mention `--with-agents` (generates AGENTS.md / CLAUDE.md). - symfony/caddy.md: describe the Caddyfile actually shipped by symfony-docker (FrankenPHP worker, @phproute) and the Hydra+Mercure Link header injected through CADDY_SERVER_EXTRA_DIRECTIVES. Move the single-domain API+PWA proxy (route{}, @pwa, PWA_UPSTREAM) into an opt-in section. - core/getting-started.md, laravel/index.md: use `api-platform <name>` (drop the non-existent `new` subcommand).
1 parent 8d48d6d commit 496b2f8

4 files changed

Lines changed: 151 additions & 66 deletions

File tree

core/getting-started.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ library manually.
1010
1111
### Symfony
1212

13-
If you are starting a new project, the easiest way to get API Platform up is to install
14-
[API Platform for Symfony](../symfony/index.md).
13+
If you are starting a new project, the easiest way to get API Platform up is to use the CLI:
14+
`api-platform my-project --framework=symfony`. See [API Platform for Symfony](../symfony/index.md)
15+
for details.
1516

1617
It comes with the API Platform core library integrated with
1718
[the Symfony framework](https://symfony.com), [the schema generator](../schema-generator/index.md),

laravel/index.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ cd my-api-platform-laravel-app
8787

8888
## Installing API Platform
8989

90+
> [!TIP] The API Platform CLI can automate all of the steps below. To scaffold a new Laravel project
91+
> with API Platform already installed, run:
92+
>
93+
> ```console
94+
> api-platform my-project --framework=laravel
95+
> ```
96+
>
97+
> This detects the `laravel` installer if available, creates the project, requires
98+
> `api-platform/laravel`, and runs `php artisan api-platform:install` for you. The manual steps
99+
> below remain valid for adding API Platform to an existing Laravel project.
100+
90101
In your Laravel project, install the API Platform integration for Laravel:
91102
92103
```console

symfony/caddy.md

Lines changed: 130 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,120 @@
11
# Configuring the Caddy Web Server with Symfony
22

3-
[The API Platform Symfony variant](index.md), when generated with Docker, is shipped with
4-
[the Caddy web server](https://caddyserver.com). The build contains the
5-
[Mercure](../core/mercure.md) and the [Vulcain](https://vulcain.rocks) Caddy modules.
3+
When you scaffold a project with the [API Platform CLI](index.md), the Symfony application is built
4+
on top of [`symfony-docker`](https://github.com/dunglas/symfony-docker), which ships
5+
[the Caddy web server](https://caddyserver.com) running [FrankenPHP](https://frankenphp.dev). The
6+
build contains the [Mercure](../core/mercure.md) and the [Vulcain](https://vulcain.rocks) Caddy
7+
modules.
68

7-
Caddy is positioned in front of the web API and of the Progressive Web App (PWA). It routes requests
8-
to either service depending on the value of the `Accept` HTTP header or the path of the request.
9+
The Caddyfile lives at `api/frankenphp/Caddyfile`.
910

10-
Using the same domain to serve the API and the PWA
11-
[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/).
11+
## How the CLI Serves the API and the PWA
1212

13-
## Why `route {}` Is Required
13+
By default the API and the Progressive Web App (PWA) are served **separately**:
14+
15+
- Caddy serves the **API** on `https://localhost`.
16+
- When you scaffold with `--with-pwa`, the Next.js application runs **standalone** with `pnpm dev`
17+
on `http://localhost:3000`. It calls the API cross-origin using the `NEXT_PUBLIC_API_ENTRYPOINT`
18+
value written to `pwa/.env.local`, and the CLI installs and configures
19+
[`nelmio/cors-bundle`](https://github.com/nelmio/NelmioCorsBundle) on the API so those
20+
cross-origin requests are allowed.
21+
22+
This keeps the two applications independent and requires no Caddy configuration. If you prefer to
23+
serve both on the **same domain** through Caddy — which
24+
[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/)
25+
— see
26+
[Serving the API and the PWA on the Same Domain](#serving-the-api-and-the-pwa-on-the-same-domain)
27+
below.
28+
29+
## The Shipped Caddyfile
30+
31+
Out of the box, `api/frankenphp/Caddyfile` routes every request to the PHP application. The relevant
32+
part of the site block looks like this:
33+
34+
```caddy
35+
{$SERVER_NAME:localhost} {
36+
root /app/public
37+
encode zstd br gzip
38+
39+
mercure {
40+
# ...Mercure hub configuration...
41+
}
42+
43+
vulcain
44+
45+
# Extra directives injected by the CLI (see "The Link Header" below)
46+
{$CADDY_SERVER_EXTRA_DIRECTIVES}
47+
48+
@phpRoute {
49+
not path /.well-known/mercure*
50+
not file {path}
51+
}
52+
rewrite @phpRoute index.php
53+
54+
@frontController path index.php
55+
php @frontController {
56+
worker {
57+
file ./public/index.php
58+
}
59+
}
60+
61+
file_server {
62+
hide *.php
63+
}
64+
}
65+
```
66+
67+
Any request that is not an existing static file and is not a Mercure subscription is rewritten to
68+
`index.php` and handled by Symfony through the FrankenPHP worker.
69+
70+
## The `Link` Header
71+
72+
The CLI adds a Hydra + Mercure `Link` header to every response. Rather than editing the Caddyfile
73+
directly, it injects the directive through the `CADDY_SERVER_EXTRA_DIRECTIVES` environment variable
74+
in `api/compose.yaml`, inside a recipe block:
75+
76+
```yaml
77+
###> api-platform/api-platform ###
78+
CADDY_SERVER_EXTRA_DIRECTIVES:
79+
'header ?Link `</docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation",
80+
</.well-known/mercure>; rel="mercure"`'
81+
###< api-platform/api-platform ###
82+
```
83+
84+
The `?` prefix means the header is only set when not already present in the response — a PHP
85+
response that sets its own `Link` header is not overwritten.
86+
87+
Setting it at the Caddy level serves two purposes:
88+
89+
1. **API discoverability**: every response advertises the Hydra API documentation URL, allowing
90+
clients to auto-discover the API.
91+
2. **Mercure subscription**: every response advertises the Mercure hub URL, so clients can subscribe
92+
to real-time updates without any application code.
93+
94+
## Serving the API and the PWA on the Same Domain
95+
96+
If you want Caddy to serve both the API and the Next.js application on a single domain, you need to
97+
forward HTML requests to the PWA and keep API requests on PHP. This is **not** configured by the
98+
CLI; the steps below add it on top of a scaffolded project.
99+
100+
### 1. Make the PWA reachable from the Caddy container
101+
102+
Caddy runs inside the `php` container, so it must be able to reach the Next.js server. Either:
103+
104+
- run the PWA in a Docker service named `pwa` listening on port `3000` (then the upstream is
105+
`pwa:3000`), or
106+
- keep running `pnpm dev` on the host and target it with `host.docker.internal:3000`.
107+
108+
Declare the upstream as an environment variable for the `php` service in `api/compose.yaml`:
109+
110+
```yaml
111+
services:
112+
php:
113+
environment:
114+
PWA_UPSTREAM: pwa:3000
115+
```
116+
117+
### 2. Wrap the routing directives in a `route {}` block
14118

15119
Caddy processes directives in a
16120
[predefined global order](https://caddyserver.com/docs/caddyfile/directives#directive-order), not in
@@ -21,7 +125,8 @@ Next.js.
21125

22126
Wrapping the directives in a `route {}` block enforces **strict first-match-wins evaluation in file
23127
order**. The first directive that matches a request wins, and Caddy stops evaluating the rest. This
24-
is what makes the `@pwa` proxy check run before the PHP rewrite:
128+
makes the `@pwa` proxy check run before the PHP rewrite. Replace the `@phpRoute … file_server`
129+
section of the site block with:
25130

26131
```caddy
27132
route {
@@ -34,16 +139,20 @@ route {
34139
35140
# 3. Run PHP for index.php
36141
@frontController path index.php
37-
php @frontController
142+
php @frontController {
143+
worker {
144+
file ./public/index.php
145+
}
146+
}
38147
39148
# 4. Serve remaining static files
40149
file_server { hide *.php }
41150
}
42151
```
43152

44-
## The `@pwa` Matcher
153+
### 3. Define the `@pwa` matcher
45154

46-
The `@pwa` named matcher is a
155+
Add a `@pwa` named matcher a
47156
[CEL (Common Expression Language) expression](https://caddyserver.com/docs/caddyfile/matchers#expression)
48157
that decides which requests are forwarded to the Next.js application:
49158

@@ -62,7 +171,7 @@ that decides which requests are forwarded to the Next.js application:
62171
The expression has three independent clauses joined by `||`. A request matches `@pwa` if **any**
63172
clause is true.
64173

65-
### Clause 1: HTML requests that are not API paths
174+
#### Clause 1: HTML requests that are not API paths
66175

67176
A browser navigating to any URL sends `Accept: text/html, */*`. This clause forwards those requests
68177
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):
79188
| `/_profiler*`, `/_wdt*` | Symfony Web Debug Toolbar and Profiler |
80189
| `*.json*`, `*.html`, `*.csv`, `*.yml`, `*.yaml`, `*.xml` | Content-negotiated formats served by the API |
81190

82-
### Clause 2: Next.js static assets and well-known files
191+
#### Clause 2: Next.js static assets and well-known files
83192

84193
```caddy
85194
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`
89198
and `/__next/*` are the internal asset paths used by the Next.js runtime for JavaScript chunks, CSS,
90199
images, and hot module replacement updates in development.
91200

92-
### Clause 3: React Server Components requests
201+
#### Clause 3: React Server Components requests
93202

94203
```caddy
95204
query({'_rsc': '*'})
@@ -100,57 +209,15 @@ Next.js uses the `_rsc` query parameter internally for
100209
data fetching. These requests do not carry `text/html` in their `Accept` header, so they would miss
101210
clause 1 without this dedicated check.
102211

103-
## The `Link` Header
104-
105-
```caddy
106-
header ?Link `</docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation", </.well-known/mercure>; rel="mercure"`
107-
```
108-
109-
This directive is placed at the **site block level**, outside the `route {}` block, so it applies to
110-
every response regardless of whether it came from PHP or Next.js. The `?` prefix means the header is
111-
only set when not already present in the response — a PHP response that sets its own `Link` header
112-
is not overwritten.
113-
114-
Setting this at the Caddy level serves two purposes:
115-
116-
1. **API discoverability**: every response advertises the Hydra API documentation URL, allowing
117-
clients to auto-discover the API.
118-
2. **Mercure subscription**: every response advertises the Mercure hub URL, so clients can subscribe
119-
to real-time updates without any application code.
120-
121-
The Next.js application does not need to set these headers itself — they arrive on every response
122-
automatically.
123-
124-
## The `PWA_UPSTREAM` Environment Variable
125-
126-
```caddy
127-
reverse_proxy @pwa http://{$PWA_UPSTREAM}
128-
```
129-
130-
`PWA_UPSTREAM` is resolved at runtime from the container environment. In `compose.yaml` it is set to
131-
`pwa:3000`, where `pwa` is the Docker Compose service name and `3000` is the default port of the
132-
Next.js server.
133-
134-
When the `pwa` service is not running (for example in an API-only project), Caddy returns a
135-
`502 Bad Gateway` for any request matching `@pwa`. To run without a Next.js frontend, comment out
136-
that line in the Caddyfile:
137-
138-
```caddy
139-
route {
140-
# Comment the following line if you don't want Next.js to catch requests for HTML documents.
141-
# In this case, they will be handled by the PHP app.
142-
# reverse_proxy @pwa http://{$PWA_UPSTREAM}
143-
144-
@phpRoute { not path /.well-known/mercure*; not file {path} }
145-
rewrite @phpRoute index.php
146-
@frontController path index.php
147-
php @frontController
148-
file_server { hide *.php }
149-
}
150-
```
212+
When the PWA upstream is unreachable, Caddy returns a `502 Bad Gateway` for any request matching
213+
`@pwa`. To temporarily fall back to PHP-rendered HTML, comment out the `reverse_proxy @pwa` line
214+
inside the `route {}` block.
151215

152216
## Adjusting the Routing Rules
153217

218+
The rules below assume you have enabled single-domain serving and therefore have a `@pwa` matcher to
219+
tweak.
220+
154221
### Routing an admin path to PHP
155222

156223
If you use EasyAdmin, SonataAdmin, or a custom Symfony controller that serves HTML pages, add the

symfony/index.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ When the PWA and the admin are enabled, the installer creates a project director
126126
`api/` subdirectory (the Symfony API) alongside a `pwa/` directory (the Next.js application) and an
127127
`admin/` directory (the React-admin SPA). The rest of this tutorial assumes this layout.
128128

129+
By default the installer also writes `AGENTS.md` and `CLAUDE.md` instruction files so AI coding
130+
agents (Claude Code, Cursor, GitHub Copilot, …) know how to work with API Platform in your project.
131+
Pass `--no-with-agents` to skip them.
132+
129133
API Platform is shipped with a [Docker](https://docker.com) definition that makes it easy to get a
130134
containerized development environment up and running. If you do not already have Docker on your
131135
computer, it's the right time to [install it](https://docs.docker.com/get-docker/).
@@ -167,7 +171,9 @@ This starts the following services:
167171
168172
When generated with `--with-pwa`, the Next.js application lives in the sibling `pwa/` directory. It
169173
is **not** part of the Docker Compose stack: you run it separately with its own development server
170-
(see [A Next.js Web App](#a-nextjs-web-app) below).
174+
(see [A Next.js Web App](#a-nextjs-web-app) below). To serve the API and the PWA on the same domain
175+
through Caddy instead, see
176+
[Configuring the Caddy Web Server](caddy.md#serving-the-api-and-the-pwa-on-the-same-domain).
171177
172178
The following components are available:
173179

0 commit comments

Comments
 (0)