Introduction
Ginject is a NestJS-inspired dependency-injection web framework for Go. Module-based architecture with production-grade execution context propagation.
Introduction
Ginject is a web framework for Go that brings the architectural patterns of NestJS to the Go ecosystem — dependency injection, module composition, declarative routing, and a layered request pipeline — while staying idiomatic to Go's concurrency and context models.
Philosophy
Ginject is built around three beliefs:
- Architecture beats performance tweaks — organizing code into modules and controllers makes it easier to test, extend, and reason about than a flat handler registry.
- Context propagation is non-negotiable — every goroutine spawned during a request must be reachable by
context.Done(), or you have goroutine leaks. - Convention over configuration — naming a handler
READ_BY_IDshould be enough to registerGET /:id. No annotations and no build-time code generation required.
Multi Pipelines, One Stage
Ginject's core architectural insight is that multiple transport protocols can share a unified processing model. Whether you're building HTTP REST APIs, real-time WebSocket servers, or future transports, the application logic remains the same.
What is a Pipeline?
A pipeline represents how a request from a specific transport (HTTP, WebSocket) flows through your application:
- HTTP Pipeline: Request arrives via
GET /users/123→ flows through processing stages → sends HTTP response - WebSocket Pipeline: Client sends
{ event: "message", data: {...} }→ flows through processing stages → sends reply back
Each transport has its own transport-specific mechanics, but once the request enters the application, it follows the same processing stages.
Why Multiple Transports?
Different communication patterns have different requirements:
- HTTP: Stateless request-response cycles, perfect for public APIs
- WebSocket: Stateful bidirectional communication, perfect for real-time features (notifications, chat, collaboration)
Rather than building two separate applications, Ginject lets you use the same guards, middleware, interceptors, and handlers for both.
What is "One Stage"?
"One Stage" means all transports share the same processing stages:
Whether a request comes from HTTP or WebSocket, it passes through the same stages in the same order. This is the key to code reuse: you write a guard once, and it protects both HTTP endpoints and WebSocket events.
Example: Auth Guard for Both HTTP and WebSocket
The guard implementation is type-aware — Ginject inspects the CanActivate signature and applies the right one to the right transport. Single logic, multiple transports.
Transports
Ginject supports two primary transports out of the box. Each transport has its own characteristics, but both use the same processing stages.
HTTP
What it is: Standard HTTP/1.1 request-response protocol for REST APIs.
When to use:
- Public APIs consumed by web/mobile clients
- Stateless request-response patterns
- APIs that need to be behind CDNs or load balancers
- Browser-based applications
Lifecycle:
How it integrates with One Stage:
All HTTP handlers receive the same stages. Here's a simple example:
Routing Convention: Method names are parsed into HTTP routes:
| Handler Name | HTTP Route |
|---|---|
READ | GET / |
READ_BY_ID | GET /:id |
CREATE | POST / |
UPDATE | PUT / |
MODIFY | PATCH / |
DELETE | DELETE / |
PREFLIGHT | OPTIONS / |
Path modifiers:
BY— path parameter:READ_BY_ID_AND_ROLE→GET /:id/:roleAND— additional segment:READ_AND_ARCHIVED→GET /archivedOF— sub-resource:READ_OF_COMMENTS→GET /commentsVERSION_N— API versioning:READ_VERSION_2→ version 2 of that route
WebSocket
What it is: WebSocket protocol for persistent, full-duplex communication over TCP.
When to use:
- Real-time notifications (chat, live updates, presence)
- Collaborative features (live editing, multiplayer games)
- Server-initiated messages to clients
- Low-latency bidirectional streaming
Lifecycle:
How it integrates with One Stage:
WebSocket handlers use the same pipeline as HTTP, but with event-based routing instead of method-based:
Key differences from HTTP:
- Handshake happens once (via middleware), not per-request
- No explicit HTTP status codes — exceptions become error events
- Handlers can broadcast to multiple connections
- Event names are lowercase method names
Stages
Every request (HTTP or WebSocket) flows through these five processing stages in order. Each stage can modify the request, short-circuit the pipeline, or transform the response.
1. Middleware
Responsibility: Early-stage request processing, logging, request enrichment, short-circuiting.
When it runs: Before all other stages, runs for every request.
What it receives:
- HTTP:
*http.Request,http.ResponseWriter, continuation functionnext() - WebSocket: Initial HTTP upgrade request (handshake only)
What it can do:
- Log request details
- Add headers/data to context
- Validate/normalize request
- Call
next()to proceed to the next stage, or skip it to stop the pipeline
Available for: Both HTTP and WebSocket
How it works:
Scope:
- Global middleware: Runs for all requests
- Module middleware: Runs for all requests in a module
- Per-handler middleware: Runs only for specific handlers
2. Guard
Responsibility: Authorization and access control.
When it runs: After middleware, before interceptors.
What it receives:
- HTTP:
*ctx.HTTPContext(request context, headers, params, etc.) - WebSocket:
*ctx.WSContext(connection context, event data)
What it can do:
- Check authentication (JWT, session, API key)
- Check authorization (roles, permissions)
- Return
trueto allow (proceed to next stage) - Return
falseto deny (responds with 403 Forbidden for HTTP, sends error event for WebSocket)
Available for: Both HTTP and WebSocket (Ginject inspects the signature)
How it works:
Scope:
- Global: Protects all routes
- Per-controller: Protects all handlers in a controller
- Per-handler: Protects specific handlers only
3. Interceptor
Responsibility: Pre/post processing, response transformation, cross-cutting concerns.
When it runs:
- Pre-processing: Before handler
- Post-processing: After handler (via pipe)
What it receives: *ctx.HTTPContext (or *ctx.WSContext), *aggregation.Aggregation object
What it can do:
- Perform pre-processing (e.g., parameter validation)
- Wrap the handler execution
- Transform the response
- Handle side effects (caching, logging)
Available for: Both HTTP and WebSocket
How it works — Interceptors wrap handler execution:
Scope: Global or per-controller (like middleware and guards).
4. Handler
Responsibility: Business logic, the main application handler.
When it runs: After all pre-processing stages (middleware → guard → interceptor).
What it receives: Parameters injected by type:
*ctx.HTTPContext— request contextctx.Body— parsed JSON bodyctx.Param— path parameters (:id)ctx.Query— query string (?key=value)ctx.Header— HTTP headersctx.Form— form datactx.File— uploaded files- Providers (services, repositories) — injected automatically by type
What it can do:
- Execute business logic
- Call services/providers
- Return data (automatically serialized to JSON)
- Throw exceptions (will be caught by exception filters)
Available for: Both HTTP and WebSocket
How it works:
5. Exception Filter
Responsibility: Error handling, converting exceptions to responses.
When it runs: When a panic occurs anywhere in the pipeline.
What it receives:
*ctx.HTTPContext(or*ctx.WSContext)*exception.Exception(the error)
What it can do:
- Log the error
- Convert to HTTP status code + JSON response (HTTP)
- Send error event to client (WebSocket)
- Custom error formatting
Available for: Both HTTP and WebSocket
How it works:
Built-in exceptions:
Throw from anywhere:
Scope: Global or per-controller.
Complete Pipeline Example
Here's how all stages work together:
Pipeline flow for POST /users (with AuthGuard):
Core Concepts Reference
| Concept | Description |
|---|---|
| Module | Unit of organization. Groups controllers and providers. Can be imported by other modules. |
| Controller | Handles HTTP or WebSocket routes. Embeds common.REST or common.WS. |
| Provider | Injectable service. Any struct implementing NewProvider() core.Provider. |
| HTTPContext | Request-scoped context with request/response data. Available only in HTTP handlers. |
| WSContext | Connection-scoped context for WebSocket. Available only in WebSocket handlers. |
| Middleware | Runs before guards, can short-circuit the pipeline. |
| Guard | Decides whether a request is authorized to proceed. |
| Interceptor | Wraps the handler — pre/post processing, response transformation. |
| ExceptionFilter | Catches panics and translates them to HTTP/WebSocket responses. |
| Pipe/DTO | Transforms and validates handler parameters before injection. |
Quick Overview
Module Graph
Providers declared in a module are available only within that module unless exported or the module is marked IsGlobal: true.