Skip to content

API DSL Syntax

The goctl API DSL is a concise language for describing HTTP services. A .api file is the single source of truth: running goctl api go regenerates all handlers, routes, and type definitions from it.

syntax = "v1" // required version declaration
info ( // optional metadata block
title: "User API"
version: "1.0"
)
import "shared.api" // import other .api files
type (...) // type definitions
service name-api { // service block
@server (...)
@handler HandlerName
method /path (RequestType) returns (ResponseType)
}

Types map directly to Go structs. Use standard Go struct tags:

type (
LoginReq {
Username string `json:"username"`
Password string `json:"password"`
}
LoginResp {
Token string `json:"token"`
Expires int64 `json:"expires"`
}
// Path parameter: /user/:id
UserReq {
Id int64 `path:"id"`
}
// Query parameter: /search?keyword=foo&page=1
SearchReq {
Keyword string `form:"keyword"`
Page int `form:"page,default=1"`
}
)
TagSourceExample
jsonRequest/response body (POST/PUT)json:"username"
pathURL path parameterpath:"id"
formURL query string (GET)form:"page,default=1"
headerHTTP request headerheader:"Authorization"

Append ,optional to make a field optional (zero value used if absent):

type SearchReq {
Keyword string `form:"keyword"`
Page int `form:"page,optional"`
Size int `form:"size,default=20"`
}
service user-api {
@server (
jwt: Auth // enable JWT middleware, config key = Auth
middleware: AccessLog,Cors // apply named middleware
prefix: /v1 // URL prefix for all routes in this block
timeout: 3s // per-request timeout
)
@handler Login
post /user/login (LoginReq) returns (LoginResp)
@handler GetUser
get /user/:id (UserReq) returns (UserResp)
@handler DeleteUser
delete /user/:id (UserReq) // no response body
}

get / post / put / patch / delete / head

Group routes by auth requirement:

service user-api {
// Public routes
@handler Login
post /user/login (LoginReq) returns (LoginResp)
}
service user-api {
@server (
jwt: Auth
)
// Private routes (JWT required)
@handler GetProfile
get /user/profile () returns (ProfileResp)
}

Split large APIs across files:

main.api
syntax = "v1"
import (
"user.api"
"order.api"
)

All imported files must use syntax = "v1" and must not contain a service block (or only one service name across all imports).

syntax = "v1"
type (
RegisterReq {
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email"`
}
RegisterResp {
Id int64 `json:"id"`
}
UserInfoReq {
Id int64 `path:"id"`
}
UserInfoResp {
Id int64 `json:"id"`
Username string `json:"username"`
}
)
service user-api {
@handler Register
post /user/register (RegisterReq) returns (RegisterResp)
}
service user-api {
@server (
jwt: Auth
prefix: /api/v1
)
@handler GetUserInfo
get /user/:id (UserInfoReq) returns (UserInfoResp)
}

Generate:

Terminal window
goctl api go -api user.api -dir .

Proto DSL syntax →