On this page
The real question behind “should we use Go?”
Most teams that ask us about Go do not want to rewrite their Laravel app. They have one endpoint that is slow, expensive or unstable under load, and they have heard that Go handles that kind of work well. It often does. But the question is not whether Go is faster than PHP. The question is whether this endpoint, in this app, is worth a second service.
We build both Laravel platforms and Go services, so we see the question from both sides. In most cases the Laravel app stays the product, and Go takes one narrow, well-defined path where it clearly pays off.
Signals that justify moving an endpoint to Go
We look for at least one of these, backed by numbers from production or a realistic load test, not a hunch.
- A measured hot path under high concurrency. One route takes a large share of traffic, and its response times climb as concurrent requests grow, even after the database work is tuned.
- CPU-bound work in the request. Parsing, scoring, geo calculations or data transformation that keeps a PHP worker busy for most of the request time.
- Long-lived connections or streaming. WebSockets, server-sent events or large streamed responses, where holding one PHP worker per open connection does not scale well.
- A strict latency target. A contract or a partner integration requires a response time that caching, queues and a long-running PHP server such as Laravel Octane still cannot meet.
If none of these apply, Go will probably make the endpoint a little faster and the system a lot more complicated.
What a second service really costs
The Go code is usually the cheap part. The ongoing cost sits around it.
| Laravel only | Laravel plus a Go service | |
|---|---|---|
| Deploys | One pipeline | Two pipelines, versioned together |
| Observability | One set of logs and metrics | Traces across both services |
| Authentication | Built in | Must match Laravel exactly |
| Data | One owner | A shared contract to keep in sync |
| Team skills | PHP | PHP and Go, with someone on call for both |
Team skills matter most in the long run. If nobody on your side will own the Go service after launch, plan for that on day one: documentation, a runbook and either a handover or a retainer.
What to try in Laravel first
A slow endpoint can often be fixed without leaving PHP, so we work through this list before we propose a split.
- Profile the request. Find where the time goes: database, external HTTP calls, serialisation or framework boot. Guessing wastes more time than measuring.
- Fix the queries. N+1 queries, missing indexes and loading full models where a few columns would do are the most common causes.
- Cache what can be cached. Responses, computed values or lookups that change rarely, with clear invalidation.
- Move work to queues. Emails, webhooks, reports and anything the user does not need to wait for.
- Try a long-running PHP server. Laravel Octane keeps the app in memory between requests, which removes the boot cost on every request. Check your code for state that leaks between requests first.
If the endpoint is still the bottleneck after these steps, you now have a clear profile of why. That profile is also the brief for the Go service.
Move the endpoint, not the app. The Laravel app stays the product; Go takes one path where it clearly pays off.
Step 1: route one path at the proxy
Clients should not notice the split. Keep the same domain and URL, and send one path to the Go service at the reverse proxy. Everything else still goes to Laravel.
location /api/search/ {
proxy_pass http://go_search:8080;
proxy_set_header Host $host;
proxy_set_header X-Request-Id $request_id;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}Passing a request ID lets you follow one request through both services in your logs. Rolling back is a one-line proxy change.
Step 2: share authentication
The Go service must accept exactly the same callers as Laravel, no more and no fewer. Do not invent a second login. Validate what Laravel already issues: if your API uses signed tokens such as JWTs, Go checks the same signature and claims. If it uses opaque tokens, Go can look up the token hash in the same table read-only, or ask Laravel through a small internal endpoint and cache the answer briefly. Write tests for expired, revoked and missing tokens on both sides.
Step 3: keep data ownership clear
Two services writing to the same tables is where most splits go wrong. We keep one owner per table. The usual shapes are:
- Read-only access. Go reads from the Laravel database, ideally a replica, with a database user that has no write permissions.
- Events. Laravel publishes changes to a queue or message bus, and Go keeps its own read model up to date.
Either way, Laravel migrations now affect another service. Treat the columns Go reads as a public contract, and review schema changes with that in mind.
Step 4: a small Go handler
For one endpoint you rarely need a framework. Since Go 1.22 the standard library router matches methods and path wildcards, so a focused service stays small. See the ServeMux documentation for the pattern syntax.
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/search/{term}", func(w http.ResponseWriter, r *http.Request) {
term := r.PathValue("term")
results, err := search(r.Context(), term)
if err != nil {
http.Error(w, "search failed", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"data": results})
})
srv := &http.Server{Addr: ":8080", Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second}
log.Fatal(srv.ListenAndServe())
}Note the server timeouts and the request context passed down to the search. Timeouts on every call are part of every Go service we ship, so a slow dependency cannot hold connections open forever.
Step 5: contract tests on the response shape
Your frontend and mobile apps depend on the exact JSON the Laravel endpoint returns today: field names, types, nulls, pagination and error format. Before switching traffic, record real responses from the Laravel version and assert that the Go version returns the same shape for the same inputs. Keep those tests in CI so the two can never drift apart silently.
Step 6: roll out gradually and measure before and after
Record the baseline first: latency percentiles, error rate and resource use for the endpoint on the Laravel version. Then send a small share of traffic to Go, for example with weighted upstreams at the proxy or a feature flag, compare, and increase step by step. Keep the Laravel route in place until the Go version has run at full traffic for a while. If the numbers do not improve, you roll back and you have lost little.
Summary
Go is a good tool for a narrow set of problems: hot paths under high concurrency, CPU-bound work, long-lived connections and strict latency targets. Try profiling, query fixes, caching, queues and Octane first. When a split is justified, route one path at the proxy, share auth, keep one owner per table, test the contract and measure before and after. If one endpoint is holding your app back and you want a second opinion, send us a short brief.
Frequently asked questions
Is Go faster than Laravel?
For CPU-bound work and many concurrent connections, a Go service usually uses fewer resources per request than PHP. But most slow Laravel endpoints are slow because of queries, external calls or missing caching, and those problems follow you into Go. Measure first.
Should I rewrite my Laravel app in Go?
Rarely. A full rewrite is expensive and risky. Moving one measured hot path into a focused Go service behind the same URL gives you most of the benefit while the Laravel app stays the product.


