Skip to content

Commit 7105024

Browse files
feat(08): add TypeScript server generator (#121)
* feat(08): add TypeScript server generator (protoc-gen-ts-server) Extract shared TS type mapping into internal/tscommon/ and add a new framework-agnostic TypeScript HTTP server generator that uses the Web Fetch API (Request → Promise<Response>). - Extract tscommon from tsclientgen (types.go, helpers.go) — zero behavior change - Add tsservergen with handler interfaces, route descriptors, header validation - Add 13 golden tests + cross-generator consistency tests vs ts-client - Update CLAUDE.md with ts-server documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(08): add path param merge, generation-time validation, and fullstack demo Path params are now merged into the request body after parsing (body.id = pathParams["id"]) so handlers receive complete request objects. Generation fails fast for invalid protos: unmatched path params and unreachable fields on GET/DELETE methods. Adds examples/ts-fullstack-demo/ showing TS client + TS server generated from the same proto, with CRUD, query params, unwrap, header validation, and custom error handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add protoc-gen-ts-server to all project documentation Update README, architecture, getting-started, client-generation, examples index, and contributing docs to reflect the new TypeScript HTTP server generator and the ts-fullstack-demo example. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(08): add interactive browser UI with live server log streaming Serves an HTML dashboard at / with clickable buttons for all CRUD operations, query params, unwrap, and error handling scenarios. Server logs stream to the browser in real-time via SSE so you can see handler activity as you click. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(08): add proto-defined custom errors and comprehensive request/response logging Generator: CollectServiceMessages now includes proto messages ending with "Error", mirroring Go's convention where error messages auto-implement the error interface. Both ts-server and ts-client generators emit interfaces for NotFoundError, LoginError, etc. Demo: proto-defined NotFoundError and LoginError with full round-trip — server implements generated interfaces, client parses ApiError.body using generated types. Added Login Error (401) scenario to browser UI. Server and client now log full request/response details (method, path, headers, body, status, duration) with colored terminal output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add TypeScript proto-defined custom error documentation Update all project docs to cover the new feature where both TS generators (ts-client, ts-server) emit TypeScript interfaces for proto messages ending with "Error". Documents the full pattern: proto definition → generated interfaces → server implements + client parses for type-safe error handling across the wire. Updated: CLAUDE.md, architecture.md, client-generation.md, examples README, http-generation.md, validation.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2ebbcde commit 7105024

58 files changed

Lines changed: 7718 additions & 666 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

7-
This is `sebuf`, a specialized Go protobuf toolkit for building HTTP APIs. It consists of four complementary protoc plugins that together enable modern, type-safe API development:
7+
This is `sebuf`, a specialized Go protobuf toolkit for building HTTP APIs. It consists of five complementary protoc plugins that together enable modern, type-safe API development:
88

99
- **`protoc-gen-go-http`**: Generates HTTP handlers, routing, request/response binding, and automatic validation
1010
- **`protoc-gen-go-client`**: Generates type-safe Go HTTP clients with functional options pattern
1111
- **`protoc-gen-ts-client`**: Generates TypeScript HTTP clients with full type safety, header helpers, and error handling
12+
- **`protoc-gen-ts-server`**: Generates TypeScript HTTP server handlers using the Web Fetch API (Request/Response), framework-agnostic
1213
- **`protoc-gen-openapiv3`**: Creates comprehensive OpenAPI v3.1 specifications
1314

1415
The toolkit enables developers to build HTTP APIs directly from protobuf definitions without gRPC dependencies, targeting web and mobile API development with built-in request validation.
@@ -21,10 +22,13 @@ The project follows a clean Go protoc plugin architecture with separated concern
2122
- **cmd/protoc-gen-go-http/**: HTTP handler generator entry point
2223
- **cmd/protoc-gen-go-client/**: Go HTTP client generator entry point
2324
- **cmd/protoc-gen-ts-client/**: TypeScript HTTP client generator entry point
25+
- **cmd/protoc-gen-ts-server/**: TypeScript HTTP server generator entry point
2426
- **cmd/protoc-gen-openapiv3/**: OpenAPI specification generator entry point
2527
- **internal/httpgen/**: HTTP handler generation logic, annotations, and header validation middleware
2628
- **internal/clientgen/**: Go HTTP client generation logic and annotations
27-
- **internal/tsclientgen/**: TypeScript HTTP client generation logic, type mapping, and annotations
29+
- **internal/tscommon/**: Shared TypeScript type mapping and generation (used by ts-client and ts-server)
30+
- **internal/tsclientgen/**: TypeScript HTTP client generation logic
31+
- **internal/tsservergen/**: TypeScript HTTP server generation logic, header validation, route creation
2832
- **internal/openapiv3/**: OpenAPI generation logic, type mapping, and header parameter generation
2933
- **proto/sebuf/http/**: HTTP annotation definitions including headers.proto for header validation
3034
- **scripts/**: Test automation and build scripts
@@ -34,8 +38,10 @@ The project follows a clean Go protoc plugin architecture with separated concern
3438
1. **HTTP Handler Generator** (`internal/httpgen/generator.go:22`): Generates HTTP handlers, request binding, routing configuration, automatic body validation, and header validation middleware
3539
2. **Go HTTP Client Generator** (`internal/clientgen/generator.go:13`): Generates type-safe Go HTTP clients with functional options pattern, automatic request/response marshaling, and error handling
3640
3. **TypeScript HTTP Client Generator** (`internal/tsclientgen/generator.go`): Generates TypeScript HTTP clients with typed interfaces, service/method header helpers, query parameter encoding, path parameter substitution, and structured error handling (ValidationError/ApiError)
37-
4. **OpenAPI Generator** (`internal/openapiv3/generator.go:53`): Creates comprehensive OpenAPI v3.1 specifications from protobuf definitions with full header parameter support, generating one file per service for better organization
38-
4. **HTTP Annotations** (`proto/sebuf/http/annotations.proto`): Custom protobuf extensions for HTTP configuration
41+
4. **TypeScript HTTP Server Generator** (`internal/tsservergen/generator.go`): Generates framework-agnostic TypeScript HTTP server handlers using the Web Fetch API (`Request``Promise<Response>`), with route descriptors, header validation, query/body parsing, and error handling
42+
5. **OpenAPI Generator** (`internal/openapiv3/generator.go:53`): Creates comprehensive OpenAPI v3.1 specifications from protobuf definitions with full header parameter support, generating one file per service for better organization
43+
6. **Shared TypeScript Types** (`internal/tscommon/`): Shared TypeScript type mapping, interface generation, error types, and proto-defined error message collection (messages ending with "Error") used by both ts-client and ts-server generators
44+
7. **HTTP Annotations** (`proto/sebuf/http/annotations.proto`): Custom protobuf extensions for HTTP configuration
3945
5. **Header Validation** (`proto/sebuf/http/headers.proto`): Protobuf definitions for service and method-level header validation
4046
6. **Validation System**: Automatic request body validation via buf.validate/protovalidate and header validation middleware
4147

@@ -93,11 +99,45 @@ try {
9399
if (e instanceof ValidationError) {
94100
console.log(e.violations); // Field-level validation errors
95101
} else if (e instanceof ApiError) {
96-
console.log(e.statusCode, e.message);
102+
// Parse proto-defined custom errors using generated interfaces
103+
const body = JSON.parse(e.body) as NotFoundError;
104+
console.log(body.resourceType, body.resourceId);
97105
}
98106
}
99107
```
100108

109+
**TypeScript HTTP Servers** - Framework-agnostic server with Web Fetch API:
110+
```typescript
111+
// Generated handler interface (like Go's XxxServer)
112+
export interface UserServiceHandler {
113+
createUser(ctx: ServerContext, req: CreateUserRequest): Promise<User>;
114+
getUser(ctx: ServerContext, req: GetUserRequest): Promise<User>;
115+
}
116+
117+
// Route creation — wire into any framework (Express, Hono, Bun, etc.)
118+
const routes: RouteDescriptor[] = createUserServiceRoutes(handler, {
119+
onError: (err, req) => new Response("Internal error", { status: 500 }),
120+
validateRequest: (method, body) => myValidator(method, body),
121+
});
122+
123+
// Each route: { method: "POST", path: "/api/v1/users", handler: (req) => Response }
124+
// Handlers do: validate headers → parse body/query → optional validation → call handler → JSON response
125+
126+
// Works natively in Node 18+, Deno, Bun, Cloudflare Workers
127+
// Example with Bun:
128+
Bun.serve({
129+
fetch(req) {
130+
const url = new URL(req.url);
131+
for (const route of routes) {
132+
if (req.method === route.method && matchPath(url.pathname, route.path)) {
133+
return route.handler(req);
134+
}
135+
}
136+
return new Response("Not Found", { status: 404 });
137+
},
138+
});
139+
```
140+
101141
**OpenAPI Specifications** - Comprehensive API documentation (one file per service):
102142
```yaml
103143
# UserService.openapi.yaml
@@ -479,6 +519,7 @@ service UserService {
479519
### Error Handling
480520
- **Structured Error Responses**: All errors use protobuf messages for consistent API responses
481521
- **Automatic Go Error Interface**: Any protobuf message ending with "Error" automatically implements Go's error interface for `errors.As()` and `errors.Is()` support
522+
- **Automatic TypeScript Error Interfaces**: Both TS generators (`protoc-gen-ts-client`, `protoc-gen-ts-server`) generate TypeScript interfaces for proto messages ending with "Error", enabling type-safe custom error handling across server and client
482523
- **Proto Message Error Preservation**: Custom proto error messages returned from handlers are serialized directly, preserving their structure (not wrapped in a generic Error message)
483524
- **Validation Errors (HTTP 400)**: ValidationError with field-level violations for body and header validation failures
484525
- **Handler Errors (HTTP 500)**: Error messages for service implementation failures with custom messages
@@ -490,15 +531,21 @@ service UserService {
490531

491532
**Custom Proto Error Example:**
492533
```protobuf
493-
// Define a custom error message
534+
// Define custom error messages — works across Go, TS server, and TS client
494535
message NotFoundError {
495536
string resource_type = 1;
496537
string resource_id = 2;
497538
}
539+
540+
message LoginError {
541+
string reason = 1;
542+
string email = 2;
543+
int32 retry_after_seconds = 3;
544+
}
498545
```
499546

500547
```go
501-
// Return it from your handler - it will be serialized directly
548+
// Go: Return it from your handler - it will be serialized directly
502549
func (s *Server) GetUser(ctx context.Context, req *GetUserRequest) (*User, error) {
503550
user, err := s.db.FindUser(req.Id)
504551
if err != nil {
@@ -513,6 +560,19 @@ func (s *Server) GetUser(ctx context.Context, req *GetUserRequest) (*User, error
513560
// NOT: {"message":"{\"resourceType\":\"user\",\"resourceId\":\"123\"}"}
514561
```
515562

563+
```typescript
564+
// TS Server: implement generated interface, serialize in onError hook
565+
class NotFoundError extends Error implements NotFoundErrorType {
566+
resourceType: string;
567+
resourceId: string;
568+
// ... constructor
569+
}
570+
571+
// TS Client: parse ApiError.body using generated interface
572+
const body = JSON.parse(e.body) as NotFoundError;
573+
console.log(body.resourceType, body.resourceId);
574+
```
575+
516576
## Type System
517577

518578
The plugin handles comprehensive protobuf-to-Go type mapping in `getFieldType()` (generator.go:118):
@@ -544,11 +604,14 @@ The repository contains:
544604
- **cmd/protoc-gen-go-http/**: HTTP handler plugin entry point
545605
- **cmd/protoc-gen-go-client/**: Go HTTP client plugin entry point
546606
- **cmd/protoc-gen-ts-client/**: TypeScript HTTP client plugin entry point
607+
- **cmd/protoc-gen-ts-server/**: TypeScript HTTP server plugin entry point
547608
- **cmd/protoc-gen-openapiv3/**: OpenAPI generation plugin entry point
548-
- **internal/annotations/**: Shared annotation parsing used by all 4 generators (unwrap, query params, headers, JSON mapping)
609+
- **internal/annotations/**: Shared annotation parsing used by all 5 generators (unwrap, query params, headers, JSON mapping)
549610
- **internal/httpgen/**: HTTP handler generation logic and tests
550611
- **internal/clientgen/**: Go HTTP client generation logic and tests
612+
- **internal/tscommon/**: Shared TypeScript type mapping and generation (interfaces, enums, error types)
551613
- **internal/tsclientgen/**: TypeScript HTTP client generation logic and tests
614+
- **internal/tsservergen/**: TypeScript HTTP server generation logic and tests
552615
- **internal/openapiv3/**: OpenAPI generation logic and comprehensive test suite
553616
- **examples/ts-client-demo/**: End-to-end TypeScript client example with NoteService CRUD API
554617
- **scripts/run_tests.sh**: Advanced test runner with coverage analysis and reporting

CONTRIBUTING.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,14 @@ sebuf/
179179
│ ├── protoc-gen-go-http/ # HTTP handler generator
180180
│ ├── protoc-gen-go-client/ # Go HTTP client generator
181181
│ ├── protoc-gen-ts-client/ # TypeScript HTTP client generator
182+
│ ├── protoc-gen-ts-server/ # TypeScript HTTP server generator
182183
│ └── protoc-gen-openapiv3/ # OpenAPI spec generator
183184
├── internal/ # Internal packages
184185
│ ├── httpgen/ # HTTP generation logic
185186
│ ├── clientgen/ # Go HTTP client generation logic
187+
│ ├── tscommon/ # Shared TypeScript type mapping
186188
│ ├── tsclientgen/ # TypeScript HTTP client generation logic
189+
│ ├── tsservergen/ # TypeScript HTTP server generation logic
187190
│ └── openapiv3/ # OpenAPI generation logic
188191
├── proto/ # Protobuf definitions
189192
├── http/ # Generated HTTP annotations
@@ -395,6 +398,7 @@ make test
395398
go test ./internal/httpgen/...
396399
go test ./internal/openapiv3/...
397400
go test ./internal/tsclientgen/...
401+
go test ./internal/tsservergen/...
398402

399403
# Run with coverage
400404
make test-coverage
@@ -403,6 +407,7 @@ make test-coverage
403407
UPDATE_GOLDEN=1 go test ./internal/httpgen/
404408
UPDATE_GOLDEN=1 go test ./internal/openapiv3/
405409
UPDATE_GOLDEN=1 go test ./internal/tsclientgen/
410+
UPDATE_GOLDEN=1 go test ./internal/tsservergen/
406411
```
407412

408413
### Adding New Tests

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ This starts a working HTTP API with JSON endpoints and OpenAPI docs - all genera
3232
- **HTTP handlers** from protobuf services (JSON + binary support)
3333
- **Type-safe Go HTTP clients** with functional options pattern and per-call customization
3434
- **TypeScript HTTP clients** with full type safety, header helpers, and error handling
35+
- **TypeScript HTTP servers** using the Web Fetch API, framework-agnostic (Node, Deno, Bun, Cloudflare Workers)
3536
- **Mock server generation** with realistic field examples for rapid prototyping
3637
- **Automatic request validation** using protovalidate with buf.validate annotations
3738
- **HTTP header validation** with type checking and format validation (UUID, email, datetime)
@@ -109,6 +110,10 @@ const client = new UserServiceClient("http://localhost:8080", {
109110
apiKey: "your-api-key",
110111
});
111112
const user = await client.createUser({ name: "John", email: "john@example.com" });
113+
114+
// TypeScript HTTP server (framework-agnostic, Web Fetch API)
115+
const routes = createUserServiceRoutes(handler);
116+
// Wire into any framework: Bun.serve, Deno.serve, Express, Hono, etc.
112117
```
113118

114119
## Quick setup
@@ -119,6 +124,7 @@ go install github.com/SebastienMelki/sebuf/cmd/protoc-gen-go-http@latest
119124
go install github.com/SebastienMelki/sebuf/cmd/protoc-gen-go-client@latest
120125
go install github.com/SebastienMelki/sebuf/cmd/protoc-gen-openapiv3@latest
121126
go install github.com/SebastienMelki/sebuf/cmd/protoc-gen-ts-client@latest
127+
go install github.com/SebastienMelki/sebuf/cmd/protoc-gen-ts-server@latest
122128

123129
# Try the complete example
124130
cd examples/simple-api && make demo
@@ -136,6 +142,7 @@ cd examples/simple-api && make demo
136142
- **API documentation** - OpenAPI specs that never get out of sync
137143
- **Type-safe development** - Leverage protobuf's type system for HTTP APIs
138144
- **Client generation** - Generate Go and TypeScript clients directly from your protobuf definitions
145+
- **Server generation** - Generate TypeScript HTTP servers using the Web Fetch API
139146

140147
## Built on Great Tools
141148

cmd/protoc-gen-ts-server/main.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
import (
4+
"google.golang.org/protobuf/compiler/protogen"
5+
"google.golang.org/protobuf/types/pluginpb"
6+
7+
"github.com/SebastienMelki/sebuf/internal/tsservergen"
8+
)
9+
10+
func main() {
11+
options := protogen.Options{}
12+
13+
options.Run(func(plugin *protogen.Plugin) error {
14+
plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
15+
gen := tsservergen.New(plugin)
16+
return gen.Generate()
17+
})
18+
}

0 commit comments

Comments
 (0)