Tutorial
This tutorial will teach you TypeMUX from the ground up, building a complete e-commerce API schema.
Table of Contents
- Basic Types
- Enums
- Complex Types
- Arrays and Maps
- Services and Methods
- Annotations and Attributes
- Union Types
- Namespaces
- Multiple Files and Imports
- External YAML Annotations
Basic Types
TypeMUX supports these primitive types:
string- Text dataint32- 32-bit integerint64- 64-bit integerfloat32- 32-bit floating pointfloat64- 64-bit floating pointbool- Boolean (true/false)timestamp- Date and timebytes- Binary data
Let’s create a simple product type:
type Product {
id: string
name: string
price: float64
inStock: bool
}
Enums
Enums define a set of named constants:
enum ProductCategory {
ELECTRONICS
CLOTHING
BOOKS
FOOD
OTHER
}
Add the category to the Product:
type Product {
id: string
name: string
price: float64
inStock: bool
category: ProductCategory
}
Complex Types
Types can reference other types. Let’s add a supplier:
type Supplier {
id: string
name: string
email: string
phone: string
}
type Product {
id: string
name: string
price: float64
inStock: bool
category: ProductCategory
supplier: Supplier
}
Arrays and Maps
Arrays
Use [] to define arrays:
type Product {
id: string
name: string
price: float64
inStock: bool
category: ProductCategory
supplier: Supplier
tags: []string
images: []string
}
Maps
Use map<KeyType, ValueType> for key-value pairs:
type Product {
id: string
name: string
price: float64
inStock: bool
category: ProductCategory
supplier: Supplier
tags: []string
images: []string
attributes: map<string, string>
}
Maps are converted differently by each output format:
- GraphQL: Creates strongly-typed KeyValue entry types (e.g.,
[StringStringEntry!]) - Protobuf: Uses native map syntax (e.g.,
map<string, string>) - OpenAPI: Uses
additionalPropertieswith proper typing
Nested maps are fully supported:
type Product {
// Simple map
attributes: map<string, string>
// Nested map - directly supported
nested_metadata: map<string, map<string, string>>
// Triple nested map - also supported
deep_config: map<string, map<string, map<string, int32>>>
}
Each generator handles nested maps appropriately:
- GraphQL: Auto-generates wrapper types (MapWrapper0, MapWrapper1, etc.)
- Protobuf: Uses native nested
map<string, map<string, int32>>syntax - OpenAPI: Uses nested
additionalPropertiesstructures
Services and Methods
Services define RPC-style API methods. Each method takes an input type and returns an output type.
type GetProductRequest {
id: string
}
type CreateProductRequest {
name: string
price: float64
category: ProductCategory
}
type ListProductsRequest {
category: ProductCategory
limit: int32
offset: int32
}
type ListProductsResponse {
products: []Product
total: int32
}
service ProductService {
rpc GetProduct(GetProductRequest) returns (Product)
rpc CreateProduct(CreateProductRequest) returns (Product)
rpc ListProducts(ListProductsRequest) returns (ListProductsResponse)
}
Annotations and Attributes
Annotations add metadata to control code generation.
Field Attributes
@required
Mark fields as required (non-nullable):
type Product {
id: string @required
name: string @required
price: float64 @required
inStock: bool @required
category: ProductCategory
supplier: Supplier
tags: []string
images: []string
attributes: map<string, string>
}
@default
Provide default values:
type Product {
id: string @required
name: string @required
price: float64 @required
inStock: bool @default("true")
category: ProductCategory @default("OTHER")
supplier: Supplier
tags: []string
images: []string
attributes: map<string, string>
}
@exclude
Exclude fields from specific formats:
type Product {
id: string @required
name: string @required
price: float64 @required
inStock: bool @default("true")
category: ProductCategory @default("OTHER")
supplier: Supplier
tags: []string
images: []string
attributes: map<string, string>
internalNotes: string @exclude(graphql,openapi)
}
The internalNotes field will only appear in Protobuf.
@only
Include fields only in specific formats:
type Product {
id: string @required
name: string @required
price: float64 @required
inStock: bool @default("true")
category: ProductCategory @default("OTHER")
supplier: Supplier
tags: []string
images: []string
attributes: map<string, string>
internalNotes: string @exclude(graphql,openapi)
graphqlMetadata: string @only(graphql)
}
Method Annotations
@http
Specify HTTP method:
service ProductService {
rpc GetProduct(GetProductRequest) returns (Product)
@http(GET)
rpc CreateProduct(CreateProductRequest) returns (Product)
@http(POST)
rpc UpdateProduct(UpdateProductRequest) returns (Product)
@http(PUT)
rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse)
@http(DELETE)
}
@path
Define URL path templates:
service ProductService {
rpc GetProduct(GetProductRequest) returns (Product)
@http(GET)
@path("/api/v1/products/{id}")
rpc CreateProduct(CreateProductRequest) returns (Product)
@http(POST)
@path("/api/v1/products")
rpc UpdateProduct(UpdateProductRequest) returns (Product)
@http(PUT)
@path("/api/v1/products/{id}")
rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse)
@http(DELETE)
@path("/api/v1/products/{id}")
}
@graphql
Specify GraphQL operation type:
service ProductService {
rpc GetProduct(GetProductRequest) returns (Product)
@http(GET)
@path("/api/v1/products/{id}")
@graphql(query)
rpc CreateProduct(CreateProductRequest) returns (Product)
@http(POST)
@path("/api/v1/products")
@graphql(mutation)
rpc UpdateProduct(UpdateProductRequest) returns (Product)
@http(PUT)
@path("/api/v1/products/{id}")
@graphql(mutation)
rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse)
@http(DELETE)
@path("/api/v1/products/{id}")
@graphql(mutation)
}
@success and @errors
Define HTTP status codes:
service ProductService {
rpc CreateProduct(CreateProductRequest) returns (Product)
@http(POST)
@path("/api/v1/products")
@graphql(mutation)
@success(201)
@errors(400,409,500)
rpc GetProduct(GetProductRequest) returns (Product)
@http(GET)
@path("/api/v1/products/{id}")
@graphql(query)
@success(200)
@errors(404,500)
}
Union Types
Unions represent a value that can be one of several types. This is useful for polymorphic responses.
type TextMessage {
text: string @required
timestamp: timestamp @required
}
type ImageMessage {
imageUrl: string @required
caption: string
timestamp: timestamp @required
}
type VideoMessage {
videoUrl: string @required
thumbnail: string
duration: int32
timestamp: timestamp @required
}
union Message {
TextMessage
ImageMessage
VideoMessage
}
type GetMessageRequest {
id: string @required
}
service MessageService {
rpc GetMessage(GetMessageRequest) returns (Message)
@http(GET)
@path("/api/v1/messages/{id}")
@graphql(query)
}
Generated GraphQL:
union Message @oneOf = TextMessage | ImageMessage | VideoMessage
Generated Protobuf:
message Message {
oneof value {
TextMessage text_message = 1;
ImageMessage image_message = 2;
VideoMessage video_message = 3;
}
}
Generated OpenAPI:
Message:
oneOf:
- $ref: '#/components/schemas/TextMessage'
- $ref: '#/components/schemas/ImageMessage'
- $ref: '#/components/schemas/VideoMessage'
Namespaces
Namespaces organize types and prevent naming conflicts. Use reverse domain notation:
namespace com.example.ecommerce
type Product {
id: string @required
name: string @required
}
Multiple Types with Same Name
Different namespaces can have types with the same name:
users.typemux:
namespace com.example.users
type User {
id: string @required
email: string @required
}
type Address {
street: string @required
city: string @required
}
orders.typemux:
namespace com.example.orders
type Order {
id: string @required
userId: string @required
}
type Address {
latitude: float64 @required
longitude: float64 @required
}
Both namespaces have an Address type, but they’re distinct.
Protobuf Generation with Namespaces
When using namespaces, Protobuf generates separate files per namespace:
./typemux -input schema.typemux -format protobuf -output ./generated
Creates:
generated/com.example.users.protogenerated/com.example.orders.proto
Multiple Files and Imports
Split large schemas into multiple files using imports.
types.typemux:
type User {
id: string @required
email: string @required
}
type Product {
id: string @required
name: string @required
}
services.typemux:
import "types.typemux"
type GetUserRequest {
id: string @required
}
type GetProductRequest {
id: string @required
}
service UserService {
rpc GetUser(GetUserRequest) returns (User)
@http(GET)
@path("/users/{id}")
}
service ProductService {
rpc GetProduct(GetProductRequest) returns (Product)
@http(GET)
@path("/products/{id}")
}
Cross-Namespace Imports
You can import types from different namespaces:
com/example/users/types.typemux:
namespace com.example.users
type User {
id: string @required
email: string @required
}
com/example/orders/types.typemux:
namespace com.example.orders
import "com/example/users/types.typemux"
type Order {
id: string @required
userId: string @required
user: User
}
External YAML Annotations
For large projects, you can separate annotations from your schema definition using YAML files.
schema.typemux:
type Product {
id: string
name: string
price: float64
inStock: bool
}
service ProductService {
rpc GetProduct(GetProductRequest) returns (Product)
rpc CreateProduct(CreateProductRequest) returns (Product)
}
annotations.yaml:
types:
Product:
fields:
id:
required: true
name:
required: true
price:
required: true
inStock:
required: true
default: true
services:
ProductService:
methods:
GetProduct:
http: GET
path: /api/v1/products/{id}
graphql: query
success: [200]
errors: [404, 500]
CreateProduct:
http: POST
path: /api/v1/products
graphql: mutation
success: [201]
errors: [400, 500]
Generate with annotations:
./typemux -input schema.typemux -annotations annotations.yaml -output ./generated
Multiple Annotation Files
You can merge multiple annotation files. Later files override earlier ones:
./typemux -input schema.typemux \
-annotations base-annotations.yaml \
-annotations overrides.yaml \
-output ./generated
Format-Specific Names
Override type and field names per format:
annotations.yaml:
types:
Product:
proto:
name: ProductMessage
graphql:
name: ProductType
openapi:
name: ProductSchema
fields:
id:
proto:
name: product_id
graphql:
name: productId
Qualified Names in Namespaces
When using namespaces, use fully qualified names in YAML:
annotations.yaml:
types:
com.example.users.User:
fields:
id:
required: true
com.example.orders.Order:
fields:
id:
required: true
userId:
required: true
Documentation Comments
Use triple-slash comments (///) to add documentation:
/// User account with authentication details
type User {
/// Unique user identifier
id: string @required
/// User's email address for login
email: string @required
/// Display name
username: string @required
}
/// Service for managing user accounts
service UserService {
/// Retrieves a user by their unique ID
rpc GetUser(GetUserRequest) returns (User)
@http(GET)
@path("/api/v1/users/{id}")
@graphql(query)
}
Documentation is included in all generated formats:
GraphQL:
"""
User account with authentication details
"""
type User {
"""
Unique user identifier
"""
id: String!
}
Protobuf:
// User account with authentication details
message User {
// Unique user identifier
string id = 1;
}
OpenAPI:
User:
type: object
description: User account with authentication details
properties:
id:
type: string
description: Unique user identifier
Custom Field Numbers
Control Protobuf field numbering explicitly:
type Product {
id: string = 1
name: string = 2
price: float64 = 10
category: ProductCategory = 100
}
enum ProductCategory {
ELECTRONICS = 1
CLOTHING = 2
BOOKS = 3
}
This is useful for:
- Maintaining backward compatibility
- Reserving field number ranges
- Optimizing wire format size
Complete Example
Here’s a complete e-commerce schema combining all concepts:
ecommerce.typemux:
namespace com.example.ecommerce
/// Product category enumeration
enum ProductCategory {
ELECTRONICS = 1
CLOTHING = 2
BOOKS = 3
FOOD = 4
OTHER = 5
}
/// Product availability status
enum ProductStatus {
AVAILABLE = 1
OUT_OF_STOCK = 2
DISCONTINUED = 3
}
/// Product supplier information
type Supplier {
/// Unique supplier identifier
id: string @required
/// Supplier company name
name: string @required
/// Contact email
email: string @required
/// Contact phone number
phone: string
}
/// Product in the catalog
type Product {
/// Unique product identifier
id: string @required
/// Product name
name: string @required
/// Product description
description: string
/// Price in USD
price: float64 @required
/// Product category
category: ProductCategory @required
/// Current availability status
status: ProductStatus @default("AVAILABLE")
/// Product supplier
supplier: Supplier
/// Search tags
tags: []string
/// Product image URLs
images: []string
/// Custom attributes
attributes: map<string, string>
/// Creation timestamp
createdAt: timestamp @required
/// Last update timestamp
updatedAt: timestamp @required
}
/// Request to retrieve a product by ID
type GetProductRequest {
/// Product identifier
id: string @required
}
/// Request to create a new product
type CreateProductRequest {
name: string @required
description: string
price: float64 @required
category: ProductCategory @required
supplierId: string @required
tags: []string
images: []string
attributes: map<string, string>
}
/// Request to list products with filtering
type ListProductsRequest {
/// Filter by category
category: ProductCategory
/// Filter by status
status: ProductStatus
/// Maximum number of results
limit: int32 @default("50")
/// Offset for pagination
offset: int32 @default("0")
}
/// Response containing product list
type ListProductsResponse {
/// List of products
products: []Product @required
/// Total count of products matching filter
total: int32 @required
}
/// Product catalog service
service ProductService {
/// Get a product by its ID
rpc GetProduct(GetProductRequest) returns (Product)
@http(GET)
@path("/api/v1/products/{id}")
@graphql(query)
@success(200)
@errors(404,500)
/// Create a new product
rpc CreateProduct(CreateProductRequest) returns (Product)
@http(POST)
@path("/api/v1/products")
@graphql(mutation)
@success(201)
@errors(400,500)
/// List products with optional filtering
rpc ListProducts(ListProductsRequest) returns (ListProductsResponse)
@http(GET)
@path("/api/v1/products")
@graphql(query)
@success(200)
@errors(500)
}
Generate all formats:
./typemux -input ecommerce.typemux -output ./generated
Next Steps
- Explore more Examples
- Read the Language Reference for complete syntax
- Check the Configuration Guide for advanced options