Tim API Server
A production-ready Go server framework that combines multiple gRPC services with REST APIs using Connect-Go and Vanguard-Go. Built with Buf for efficient Protocol Buffer management.
Features
- Dual Server Architecture: Runs two HTTP servers in a single binary
- External Server (port 3000): Public-facing API with authentication and authorization
- Internal Server (port 3001): Internal APIs for worker communication (no auth)
- Multi-Protocol Support: Serves both gRPC and REST APIs from the same service definitions
- Connect-Go Framework: Modern, type-safe RPC framework with excellent Go support
- REST Gateway: Automatic REST API generation from gRPC services using Vanguard and
google.api.http annotations in proto files
- OpenAPI Support: Auto-generated OpenAPI (Swagger) documentation
- Protovalidate: Request validation using
buf.build/protovalidate
- Authentication: Middleware for AuthN with Stytch
- Service Architecture: Modular design with multiple services (User, Persona, Thread, Todo)
- Streaming Support: Real-time streaming for messages and task updates
- Docker Support: Containerized deployment with multi-stage builds
Architecture
tim-api/
├── cmd/server/ # Server entry point
├── internal/ # Internal packages
│ ├── config/ # Configuration management
│ ├── middleware/ # HTTP and RPC interceptors
│ ├── server/ # Server setup and routing
│ └── services/ # Service implementations
│ ├── user/ # User service
│ └── session/ # Session service
├── pkg/ # Public packages
│ ├── logger/ # Structured logging
│ └── errors/ # Error handling
├── proto/ # Protocol buffer definitions
│ ├── user/v1/ # User service protos
│ ├── session/v1/ # Session service protos
│ └── common/v1/ # Shared types
└── gen/ # Generated code (git-ignored)
Services
User Service
- User CRUD operations
- Authentication and profile management
- RESTful endpoints at
/api/v1/users/
Session Service
- Session management for conversations
- Real-time message streaming
- RESTful endpoints at
/api/v1/sessions/
Quick Start
Prerequisites
- Go 1.23 or later
- Make
- Docker (optional)
Development
- Generate autogen go code:
just builder::gen
- Build the server:
just builder::build-tim-api
- Run the server:
./build/tim-api
The server will start two HTTP servers:
- External API:
http://localhost:3000 (public-facing with auth)
- Internal API:
http://localhost:3001 (worker communication, no auth)
Configuration
The service follows the 12-factor app principle and uses environment variables for all configuration. Copy the example environment file:
cp .env.example .env
Available environment variables:
External Server (Public API)
EXTERNAL_SERVER_ADDR - External server address (default: 127.0.0.1)
EXTERNAL_PORT - External server port (default: 3000)
CORS_ENABLED - Enable CORS for external API (default: false)
CORS_ORIGINS - Comma-separated list of allowed origins (default: *)
Internal Server (Worker Communication)
INTERNAL_SERVER_ADDR - Internal server address (default: 127.0.0.1)
INTERNAL_PORT - Internal server port (default: 3001)
General
DEV_MODE - Enable development mode (default: true)
LOG_LEVEL - Logging level: trace, debug, info, error (default: debug)
LOG_FORMAT - Log format: json or console (default: json)
Authentication
AUTHN_ENABLED - Enable authentication (default: false)
AUTHN_TYPE - Authentication type: jwt (default: jwt)
AUTHN_STYTCH_PROJECT_ID - Stytch project ID
AUTHN_STYTCH_SECRET - Stytch secret
Authorization
AUTHZ_ENABLED - Enable authorization (default: false)
AUTHZ_CERBOS_PATH - Cerbos service URL
Database
DATABASE_TYPE - Database type: postgres (default: postgres)
DATABASE_URL - Database connection string
Testing
Run tests:
just testing::test_tim_api
Run tests with coverage:
just testing::test_tim_api true
API Examples
gRPC (using Connect-Go client)
import (
"connectrpc.com/connect"
userv1 "github.com/greylabs/tim-api/gen/user/v1"
"github.com/greylabs/tim-api/gen/user/v1/userv1connect"
)
client := userv1connect.NewUserServiceClient(
http.DefaultClient,
"http://localhost:8080",
)
resp, err := client.CreateUser(context.Background(),
connect.NewRequest(&userv1.CreateUserRequest{
Email: "user@example.com",
Name: "John Doe",
}),
)
REST API
The REST API routes are defined using HTTP annotations in the proto files:
# Create a user
curl -X POST http://localhost:8080/v1/users \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"name": "John Doe",
"password": "secure_password"
}'
# Get a specific user
curl http://localhost:8080/v1/users/{user_id}
# Update a user (PATCH)
curl -X PATCH http://localhost:8080/v1/users/{user_id} \
-H "Content-Type: application/json" \
-d '{
"user": {
"name": "Jane Doe"
},
"update_mask": {
"paths": ["name"]
}
}'
# Delete a user
curl -X DELETE http://localhost:8080/v1/users/{user_id}
# Create a session
curl -X POST http://localhost:8080/v1/sessions \
-H "Content-Type: application/json" \
-d '{
"user_id": "user_123",
"name": "My Session",
"type": "SESSION_TYPE_INTERACTIVE"
}'
# List all sessions
curl http://localhost:8080/v1/sessions
# List user's sessions (alternative route)
curl http://localhost:8080/v1/users/{user_id}/sessions
# Send a message
curl -X POST http://localhost:8080/v1/sessions/{session_id}/messages \
-H "Content-Type: application/json" \
-d '{
"content": "Hello, Tim!",
"role": "MESSAGE_ROLE_USER"
}'
# Get messages from a session
curl http://localhost:8080/v1/sessions/{session_id}/messages
# Stream messages (Server-Sent Events)
curl http://localhost:8080/v1/sessions/{session_id}/messages:stream
# Execute a task with an agent
curl -X POST http://localhost:8080/v1/agents/{agent_id}/tasks \
-H "Content-Type: application/json" \
-d '{
"session_id": "session_123",
"description": "Generate a hello world program",
"input": {
"language": "go"
}
}'
# Stream task updates (Server-Sent Events)
curl http://localhost:8080/v1/tasks/{task_id}:stream
HTTP Annotations
The REST API routes are defined using google.api.http annotations in the proto files. For example:
service UserService {
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
option (google.api.http) = {
post: "/v1/users"
body: "*"
};
}
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (google.api.http) = {
get: "/v1/users/{id}"
};
}
}
This provides:
- Custom REST paths independent of RPC service names
- Support for path parameters (e.g.,
{id})
- Multiple HTTP bindings for the same RPC method
- RESTful conventions (GET, POST, PATCH, DELETE)
- Custom actions using
: notation (e.g., :stream)
Docker Deployment
Build the Docker image:
make docker-build
Run the container:
make docker-run
Linting
Run linters:
just lint
Format code:
just format
Adding New Services
- Define your service in
proto/<service>/v1/<service>.proto
- Run
just builder::proto to generate code
- Implement the service in
internal/services/<service>/
- Register in
internal/services/registry.go
- Mount in
internal/server/server.go