Skip to content

go-like compared with other tools ​

A fair comparison starts with ownership, not with a feature-count checklist. NestJS, Fastify, Hono, Elysia, Koa, and tRPC solve different parts of the TypeScript application stack. go-micro and go-kratos are Go framework references with different transport and code-generation choices. go-like is a TypeScript building-block set for explicit lifecycle, internal unary calls, provider contracts, and cross-runtime composition.

This page separates evidence levels:

  • Source means the current go-like checkout exposes the stated API or boundary.
  • Pinned external means the comparison uses the release, commit, or official documentation recorded in the research ledger. It is not a fresh benchmark or a claim that an unpinned main branch is unchanged.
  • Declared means a repository example or test lane exists. It is not a pass result.
  • Gap means the current repository does not prove a compatibility commitment.

The current go-like source baseline for this track is commit 9385dbf5b6a7d913be56a80ade359e1bf9be8675. The local research record contains a go-micro commit discrepancy: one comparison record names 9d306dcfc1a912a8a9493f31fee0bb983475258d, while the detailed fixed-version memo inspected go-micro v6.9.0 at 3c39d17fadaa9ec21b671be4afef3e63846406e6. Treat those as comparison inputs to recheck, not as a current upstream guarantee.

Position in the stack ​

ToolPrimary problemWhat it normally ownsWhat go-like would complement, not replace
NestJSConvention-driven Node application frameworkModules, providers, controllers, decorators, application context, framework lifecycle, HTTP or microservice adapterA structural lifecycle boundary or internal call contract around a native application, if an explicit bridge is written
FastifyNode HTTP server and request pipelineRoute table, hooks, plugins, encapsulation, Node listener, request/reply objectsA lifecycle or provider adapter around a Fastify-owned resource
HonoWeb Standards routing and middlewareRoutes, middleware, sub-apps, app.fetch, runtime adapter choiceCore App, explicit resource lifecycle, internal Client/Transport, discovery
ElysiaBun-first typed Web frameworkRoute tree, schema composition, decorators, hooks, Bun or Web Standard adapterCore lifecycle and internal service building blocks while retaining native Elysia behavior
KoaMinimal Node middleware kernelMiddleware stack and Node listener; router is usually externalLifecycle and internal service contracts without introducing another router
tRPCType-safe procedure layerRouter/procedure paths, input/output parsers, context factory, HTTP/Fetch/WS adaptersProvider ownership, service discovery, selector policy, explicit App lifecycle
go-microGo microservice and agent-oriented ecosystemGo Context, service/client/transport/registry/broker abstractions, provider ecosystem, and additional agent/flow/MCP/A2A scopego-like borrows some vocabulary, not Go ABI, goroutines, or transport compatibility
go-kratosGo cloud-native service frameworkApp lifecycle, Go Context, HTTP/gRPC transports, middleware, registry, config, Protobuf/code generationgo-like shares lifecycle and generated-service workflow, not Go ABI or Google gRPC runtime
go-likeExplicit TypeScript service building blocksContext, App/Server lifecycle, standard Fetch edge, internal Fetch JSON and SSE transport, managed standard gRPC, Registry/Discovery/Selector, Config/Store/Cache/Broker/HealthThe application still owns framework routes, business policy, auth, deployment, and unselected native data planes

The project is therefore not trying to win a “largest framework” comparison. Its question is whether an application needs these boundaries to be explicit and composable.

Ownership matrix ​

ConcernNestJSFastifyHono / Elysia / KoatRPCgo-like
External route tableControllers and decoratorsFastify instanceFramework instance or external routerProcedure router, not ordinary REST routesExternal framework or application
Web handler ABIAdapter-owned request/reply abstractionNode request/replyStandard Fetch is central for Hono and Web Standard adaptersFetch/Node/Express/Fastify adaptersStandard (Request) => Response | Promise<Response>
Application lifecycleApplication context and hooksready, listen, close, hooksRuntime adapter and framework lifecycle varyHost/adapter responsibilitynewApp, App.run, App.stop, hooks, structural Servers
Resource lifecycleContainer/framework hooksPlugin and server hooksApplication/runtime responsibilityApplication/adapter responsibilityExplicit Server.start(ctx) / stop(ctx) contracts and adapter ownership
Dependency compositionNest container/providersPlugin decoration and encapsulationContext/env and composition; no general DI containerExplicit context factory and router compositionExplicit constructors and functional options; no DI container
Internal transportMicroservice transports and framework adaptersNot a service discovery abstractionNot a service discovery abstractionProcedure adapters and optional WebSocketTransport, Client, Listener, defineService, SSE ServerStream
Discovery and selectionTransport-specific or externalExternalExternalExternalRegistry, Discovery, Watcher, Filters, five Selector policies
RetryFramework or provider-specificApplication/plugin-specificApplication-specificMiddleware/adapter-specificOne attempt by default; withRetry requires authorization and total attempts
StreamingFramework/provider choicesNode/Web stream choicesNative Web Streams and framework APIsAdapter-dependent HTTP/WSWeb native; internal JSON or SSE server stream; Fetch unary/server-streaming; standard gRPC four cardinalities
Global instrumentationFramework/provider integrationPlugin ecosystemMiddleware ecosystemMiddleware/adaptersExplicit wrappers; no global provider installation

The labels in the first five rows describe architecture positions, not a quality ranking. A framework owning a route table is useful when route composition is the problem. It is simply a different ownership decision from go-like leaving routes to the application.

Lifecycle and Context ​

Current go-like source defines:

ts
interface Server {
  start(ctx: Context): Promise<void>
  stop(ctx: Context): Promise<void>
}

interface App {
  run(): Promise<void>
  stop(): Promise<void>
}

The Server contract is structural. A native worker, listener, scheduler, broker subscription, logger destination, or telemetry provider can join Core if an adapter can state the admission and terminal behavior honestly.

go-like Context is also structural and uses AbortSignal internally. It exposes deadline(), done(), err(), and value(key), with constructors such as background, withCancel, withCancelCause, withTimeout, withDeadline, withoutCancel, and withValue.

This resembles Go's explicit Context-first style but is not ABI-compatible with context.Context. Context does not provide goroutines or channels, and it is not a gRPC API. The correct migration question is “where does cancellation and ownership cross this boundary?” rather than “which type name is identical?”

Core does not promise reverse-order shutdown of sibling Servers. It invokes sibling stop(ctx) calls concurrently, then joins terminal start Promises and aggregates failures. A Nest application context, Fastify plugin graph, Elysia lifecycle, or host adapter may have different ordering and terminal semantics. Compare the actual owner, not the label “graceful.”

Transport and service calls ​

go-like's internal call chain is deliberately decomposed:

text
Client
  -> Discovery snapshot, optional
  -> ordered Filter callbacks, optional
  -> Selector.select
  -> opaque ServiceEndpoint URL
  -> Transport.dial or resident logical owner
  -> fetch(Request)
  -> @go-like/server route and unary handler
  -> read Response
  -> feedback and owner release

A typed Endpoint binds Struct request and response validation to a JSON Fetch body. It is not an IDL or generated protocol. withEndpoint(...) selects a construction-time direct snapshot; direct and discovered snapshots use the same Selector. The in-process Memory Transport path remains a useful first test because it changes only the Transport provider.

NestJS's microservice transport options, tRPC procedure adapters, and Go framework transports are not interchangeable with this DAG. They may own a different route identity, serialization model, connection pool, or retry layer. A comparison should record those differences rather than mark all “RPC” boxes as equal.

Retry and streaming scope ​

The most important negative comparison is about semantics:

  • go-like calls make exactly one attempt by default.
  • withRetry(...) requires authorization: "idempotent" | "caller-approved", a positive maxAttempts, and shouldRetry.
  • The authorization is a caller declaration, not a proof of idempotency.
  • A retry may select a new endpoint because each attempt re-enters discovery and selection.
  • A response that has already been received but is followed by feedback or cleanup failure is not replayed.

The Go comparison research records different defaults and capabilities: go-micro DefaultRetries is not a simple “five total requests” statement because its loop boundary can produce six iterations when retry approval remains true; its public stream shape and default CloseSend implementation also vary by provider. go-kratos combines Protobuf/gRPC generation with HTTP streaming forms, where SSE and WebSocket have different directions and close behavior. Those are provider and architecture choices, not missing go-like flags.

For go-like:

text
Web framework or Fetch Handler
  -> Web Streams, SSE, or WebSocket behavior owned by the application/framework

go-like internal Client/Transport
  -> one JSON body, or one SSE server stream when `stream: true`
  -> not a bidirectional multi-frame protocol

@go-like/transport-grpc-buf
  -> portable Fetch: Connect/gRPC-Web unary and server-streaming
  -> /native: standard gRPC unary, server-streaming, client-streaming, bidi

A Web ReadableStream is not an internal RPC channel. Do not compare a streamed HTTP body with a multi-frame send/recv transport as if they were the same feature.

Runtime comparison ​

Runtime questiongo-like evidenceComparison consequence
Can shared code use Fetch and AbortSignal?Root Web and selected transport/config providers use standard Web APIs or injected FetchSimilar portability goals are possible, but types do not polyfill runtime behavior
Can custom PEM TLS, mTLS, ALPN, and HTTP/2 be portable through Fetch?HTTP /node and gRPC /native own explicit native behavior; root Fetch paths do not expose all controlsCompare capability subpaths and executed runtime matrices, not only package names
Does the application keep the framework router?Hono, Elysia, and H3 examples pass native Fetch handlersgo-like is complementary to framework route ownership
Does package version prove publication?The root workspace is private; package manifests use 0.0.1, which does not establish npm publicationNo npm availability or ecosystem maturity claim

The current repository contains direct source examples for Hono, Elysia, H3, and vanilla Fetch. It does not contain a current NestJS or Fastify bridge or compatibility suite. Those are migration audiences, not supported direct integrations.

Detailed comparison by tool ​

NestJS ​

NestJS is a convention-driven application framework. Its modules, providers, controllers, decorators, interceptors, pipes, and application hooks form a coherent container and request model. go-like does not provide a Nest-compatible module container or controller bridge.

A sensible integration boundary is an application-owned adapter that implements go-like's structural Server around a Nest application or host. The adapter would need to define when Nest has admitted the listener, how stop(ctx) maps to Nest close, and what happens after a timeout. The current repository does not prove such a bridge, so documentation must not show a direct newNodeServer(nestApp, ...) call.

Fastify ​

Fastify owns a route table, plugin encapsulation, hooks, and a Node listener. Its plugin graph is a useful comparison for dependency scope, but decorate is not a Nest-style general provider container. go-like does not convert Fastify's request/reply ABI into a Fetch Handler automatically, and no current Fastify bridge is tested in the repository.

Keep Fastify routes and plugins native. If go-like is adopted, write an explicit structural Server around the Fastify owner or expose a separately implemented Fetch boundary. Do not call Fastify's own request injection or native shutdown a go-like Transport or Client contract.

Hono ​

Hono is the clearest demonstrated complement. The current example creates routes in Hono, passes app.fetch to newNodeServer, and puts that host into a Core App. The route and middleware ownership stays with Hono; go-like owns the host lifecycle boundary when the application chooses it.

Elysia ​

Elysia provides a Bun-first route and schema composition model and also exposes a Web Standard handler in the relevant adapter path. Retain Elysia's route tree, decorators, derives, hooks, streams, and Bun-specific behavior. go-like can own the App and an explicit resource boundary, but it does not make .listen() a cross-runtime go-like API.

Koa ​

Koa is a small Node middleware kernel and does not bundle a router. That makes it a useful example of a framework that intentionally leaves more application composition outside the core. go-like should not fill that gap by adding a router. Keep Koa middleware and any external router native, then add a lifecycle or internal call boundary only where needed.

tRPC ​

tRPC owns a type-safe procedure router and procedure middleware. It can use Fetch, Node, Express, Fastify, or WebSocket adapters, but it is not a Registry, Selector, connection pool, or application lifecycle manager. go-like's typed Endpoint is a smaller runtime Struct binding over JSON Fetch bodies, not a competing procedure DSL or generated IDL.

go-micro and go-kratos ​

These Go projects are useful architectural references for Context-first calls, service lifecycle, Registry, Discovery, Selector, and transport vocabulary. They are not compatibility targets:

  • Go context.Context and go-like Context share explicit cancellation intent but have different runtime representations.
  • go-micro's Registry watcher model and go-like's complete replacement snapshots should not be taught as identical event streams.
  • go-kratos integrates the Google gRPC runtime; go-like integrates Protobuf-ES/Connect-ES, with portable Connect/gRPC-Web at the package root and managed standard gRPC at /native.
  • go-micro and go-kratos provider defaults, retry loops, stream half-close behavior, and selector defaults are version-specific. Use the fixed upstream commit table in the research record and recheck before publishing a new comparative release.

What to choose ​

If your primary problem is...Start with...Add go-like when...
Controllers, modules, decorators, and DINestJSYou need an explicit boundary around an existing resource or internal service call and are willing to write the adapter
Node HTTP routes, hooks, and plugin encapsulationFastifyYou need lifecycle composition beyond the host or internal unary service contracts
Web Standards routes across runtimesHonoYou need App/Server lifecycle, internal calls, or provider ownership
Bun-first schema and route compositionElysiaYou need explicit lifecycle and transport boundaries while retaining Elysia
Minimal Node middlewareKoa plus a routerYou need the missing lifecycle or internal call contract, not another router
Type-safe procedurestRPCYou also need explicit service discovery, provider ownership, or a Core lifecycle
Go microservice stackgo-micro or go-kratosYou are building a separate TypeScript composition, not a source-compatible port
Cross-runtime TypeScript service building blocksgo-likeUse only the packages and providers that solve the boundary

The right answer may be to use both systems. go-like is most useful when its explicit ownership model removes an actual ambiguity; adding every package to an otherwise complete framework application defeats the small-building-block goal.

Evidence anchors ​

The go-like claims in this page can be traced to the current tree and package entrypoints:

  • README.md for product scope and explicit exclusions;
  • packages/core/src/app.ts for App, Server, startup, stop, and timeout behavior;
  • packages/web/src/context.ts for the standard Handler and Context bridge;
  • packages/client/src/index.ts for Client options, pooling, retry, and the attempt pipeline;
  • packages/server/src/index.ts for internal unary handlers and route dispatch;
  • packages/transport/src/types.ts and packages/transport/src/endpoint.ts for the Fetch Request/Response and defineService boundaries;
  • packages/registry/src/types.ts and packages/registry/src/selector.ts for snapshots, filters, selectors, and feedback.

The research record also stores these pinned external comparison inputs:

The URLs are comparison references, not a claim that this documentation phase fetched or revalidated every upstream page. Recheck release tags or commits before changing a version-sensitive comparison statement.

Managed gRPC slow-consumer drain on Node 26.12.1 and Bun 1.4.4 is verified in the native gRPC drain matrix. The earlier Connect 2.1.2 and Bun 1.4.2 Fetch cancellation observations remain in the claims ledger; this run does not establish that those limitations are fixed.