close
Skip to main content
BERJAYA

r/golang


gRPC for Server-Client communication in 2026?
gRPC for Server-Client communication in 2026?

Hello Gophers!

TL;DR: How good is gRPC for communication between Go and Typescript (React) today? Would you recommend it? What are the pain points you faced, if any?

I am a C# developer, currently enjoying Go, net/http and sqlc.

Since Go's net/http doesn't support emitting Open API Specs, and I don't want to write them by hand or use a third party router like Huma, And I don't want to give up on Go's beauty just because of this reason. So I did some research on gRPC, and it looks like generating a client in Typescript is easy, the payloads are lighter, and it is end to end type-safe.

What's the catch? Would you recommend using gRPC for a client-server model?

Thank you!


Hank knows pizza. Copilot knows Excel.
media poster



Our Go service died from a SIGSEGV in CGO. recover() did not help.
Our Go service died from a SIGSEGV in CGO. recover() did not help.
discussion

This week we had a stable crash in one of our Go services.

We embed DuckDB in our API process through the Go driver. The driver uses Go's database/sql interface, but underneath it still crosses into DuckDB's native execution engine.

A routine federated query - joining a large MySQL table with a PostgreSQL table - triggered a SIGSEGV somewhere in the native DuckDB execution path.

The client saw:

curl: (52) Empty reply from server

The journal showed:

stream-api[12345]: terminated with signal 11 (SEGV), core dumped

The API process was gone because that is where the CGO call into DuckDB happened.

recover() does not catch this

recover() catches Go panics. A SIGSEGV from native code called through CGO is not a Go panic.

The Go runtime can turn some Go memory faults into panics. It cannot safely turn an arbitrary C/C++ segfault into a recoverable application error.

So this does nothing useful for this class of failure:

if r := recover(); r != nil {
// too late for a native SIGSEGV
}
}()

The real problem is the failure domain

Our API process had three jobs mixed together:

  • Serve HTTP.

  • Manage request/state lifecycle.

  • Run analytical SQL through an embedded native engine.

The first two are normal Go application work. The third crosses into native execution. If native execution segfaults, the whole process can die.

The fix is boring architecture:

client -> API -> query worker (DuckDB via CGO) -> data sources

If DuckDB crashes, the worker dies. The API stays alive, returns a controlled error, and restarts or replaces the worker.

The idea is not novel. Databases and browsers have used process boundaries for years to keep risky execution away from the coordinator process.

This is not about DuckDB being bad. Embedded DuckDB is still a great starting point. The problem is letting risky native execution share the same failure domain as the primary API process.

General lesson

Anything you call through CGO is inside your process's failure domain.

For small trusted calls, that can be fine. For an embedded SQL engine executing complex queries over user-controlled inputs and external data sources, the boundary becomes more important.

If a native scanner bug, extension crash, or unsafe native call can take down the API, recover() is not the fix.

A supervised worker process is.


More context: https://streams.dbconvert.com/blog/embedded-duckdb-api-process-crash/