Stream AI Replies in Laravel with PHP and SSE
§1 Build a Laravel endpoint for live AI replies
Streaming makes an AI interface feel responsive because users can read the answer while it is still being generated. In Laravel, the flow has three parts: PHP opens an upstream AI stream, your application forwards small events, and browser JavaScript appends each text fragment to the conversation.
This walkthrough uses a POST request and a response formatted as server-sent events, or SSE. The browser consumes that response with fetch(), which supports sending a prompt in the request body.
§2 Configure Fluxio credentials in Laravel
Fluxio fits this architecture because it provides a streaming-first, pay-per-token AI API relay through an OpenAI-compatible interface. One key gives your application access to multiple model providers while keeping the streaming integration consistent.
Add FLUXIO_API_KEY and FLUXIO_MODEL to your environment file. Choose an available model, such as Gemini 2.5 Flash, and copy its exact API identifier from Fluxio rather than assuming its display name is the identifier.
Expose those values through config/services.php, then read them with config('services.fluxio.key') and config('services.fluxio.model'). Keep the key on the server. Use https://api.fluxioapi.com/v1 as the base URL for OpenAI-style requests.
§3 Create a protected Laravel streaming route
Create a POST route at /chat/stream and connect it to a controller. Apply authentication and rate limiting, then validate the prompt as a required string with a reasonable length limit.
For a route using Laravel's web middleware, send the CSRF token with the browser request. Perform validation before starting the streamed response so Laravel can return ordinary validation errors with an appropriate HTTP status.
Your controller should return response()->stream($callback, 200, $headers). Set Content-Type to text/event-stream, Cache-Control to no-cache, and X-Accel-Buffering to no. The last header asks Nginx to avoid buffering this response, subject to server configuration.
§4 Request an upstream AI token stream
Inside the callback, use Laravel's HTTP client with a bearer token and withOptions(['stream' => true]). POST to https://api.fluxioapi.com/v1/chat/completions with model, messages, and stream set to true.
The messages array can contain one user message with the validated prompt. Configure connection and overall timeouts explicitly; a streamed answer may take longer than a conventional API response.
Check the upstream status before reading the stream. Obtain its PSR-7 body through toPsrResponse()->getBody(), then read manageable chunks until the body reaches EOF. Avoid calling body() or json() on the entire successful response, since that defeats incremental processing.
§5 Parse SSE events before forwarding text
Network reads do not correspond to complete events or individual tokens. One read may contain half an event, several events, or part of a UTF-8 character. Maintain a buffer and extract complete SSE records separated by blank lines, handling both LF and CRLF line endings.
For each record, collect its data fields. When the payload is [DONE], finish the stream. Otherwise, decode the JSON and inspect choices[0].delta.content. Some chunks contain metadata or an empty delta, so only forward text when it exists.
Send each fragment as a JSON-encoded SSE message, for example: data: {"text":"Hello"}, followed by two newline characters. JSON encoding preserves embedded newlines and quotation marks safely. Flush PHP's output after each event, accounting for any active output buffer.
§6 Append streamed text in the browser
Use fetch() to POST the prompt, then check response.ok before reading response.body.getReader(). Native EventSource only supports GET requests, so it does not directly fit this POST flow.
Decode bytes with TextDecoder using its streaming option. Buffer incomplete SSE records, parse each complete JSON payload, and append its text field to the answer element using textContent. Avoid inserting model output directly as HTML.
§7 Verify buffering, cancellation, and failures
- – Test through your production proxy because buffering can make a working stream appear as one delayed response.
- – Use AbortController for a Stop button, and stop upstream work when PHP detects a disconnected client.
- – Emit a defined completion event and a safe error event; once streaming starts, HTTP status changes are no longer reliable.
- – Log upstream failures privately and test empty replies, interrupted connections, and slow responses.
Sign up for Fluxio to build your first streaming Laravel chat.