shiftapi

package module
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 20, 2026 License: MIT Imports: 13 Imported by: 0

README

ShiftAPI Logo

End-to-end type safety from Go structs to TypeScript frontend.

ShiftAPI is a Go framework that generates an OpenAPI 3.1 spec from your handler types at runtime, then uses a Vite plugin to turn that spec into a fully-typed TypeScript client — so your frontend stays in sync with your API automatically.

Go Reference GolangCI Go Report Card npm

Go structs ──→ OpenAPI 3.1 spec ──→ TypeScript types ──→ Typed fetch client
   (compile time)     (runtime)         (build time)        (your frontend)

Getting Started

Scaffold a full-stack app (Go + React or Svelte):

npm create shiftapi@latest

Or add ShiftAPI to an existing Go project:

go get github.com/fcjr/shiftapi

Quick Start

package main

import (
    "log"
    "net/http"

    "github.com/fcjr/shiftapi"
)

type Person struct {
    Name string `json:"name" validate:"required"`
}

type Greeting struct {
    Hello string `json:"hello"`
}

func greet(r *http.Request, body *Person) (*Greeting, error) {
    return &Greeting{Hello: body.Name}, nil
}

func main() {
    api := shiftapi.New(shiftapi.WithInfo(shiftapi.Info{
        Title:   "Greeter API",
        Version: "1.0.0",
    }))

    shiftapi.Post(api, "/greet", greet)

    log.Println("listening on :8080")
    log.Fatal(shiftapi.ListenAndServe(":8080", api))
    // interactive docs at http://localhost:8080/docs
}

That's it. ShiftAPI reflects your Go types into an OpenAPI 3.1 spec at /openapi.json and serves interactive docs at /docs — no code generation step, no annotations.

Features

Generic type-safe handlers

Generic free functions capture your request and response types at compile time. Handlers with a body (Post, Put, Patch) receive the decoded request as a typed value. Handlers without a body (Get, Delete, Head) just receive the request.

// POST — body is decoded and passed as *CreateUser
shiftapi.Post(api, "/users", func(r *http.Request, body *CreateUser) (*User, error) {
    return db.CreateUser(r.Context(), body)
}, shiftapi.WithStatus(http.StatusCreated))

// GET — standard *http.Request, use PathValue for path params
shiftapi.Get(api, "/users/{id}", func(r *http.Request) (*User, error) {
    return db.GetUser(r.Context(), r.PathValue("id"))
})

Validation

Built-in validation via go-playground/validator. Struct tags are enforced at runtime and reflected into the OpenAPI schema.

type CreateUser struct {
    Name  string `json:"name"  validate:"required,min=2,max=50"`
    Email string `json:"email" validate:"required,email"`
    Age   int    `json:"age"   validate:"gte=0,lte=150"`
    Role  string `json:"role"  validate:"oneof=admin user guest"`
}

Invalid requests return 422 with per-field errors:

{
    "message": "validation failed",
    "errors": [
        { "field": "Name",  "message": "this field is required" },
        { "field": "Email", "message": "must be a valid email address" }
    ]
}

Supported tags: required, email, url/uri, uuid, datetime, min, max, gte, lte, gt, lt, len, oneof — all mapped to their OpenAPI equivalents (format, minimum, maxLength, enum, etc.). Use WithValidator() to supply a custom validator instance.

Error handling

Return shiftapi.Error to control the status code:

return nil, shiftapi.Error(http.StatusNotFound, "user not found")

Any non-APIError returns 500 Internal Server Error.

Route metadata

Add OpenAPI summaries, descriptions, and tags per route:

shiftapi.Post(api, "/greet", greet,
    shiftapi.WithRouteInfo(shiftapi.RouteInfo{
        Summary:     "Greet a person",
        Description: "Returns a personalized greeting.",
        Tags:        []string{"greetings"},
    }),
)

Standard http.Handler

API implements http.Handler, so it works with any middleware, httptest, and ServeMux mounting:

// middleware
wrapped := loggingMiddleware(corsMiddleware(api))
http.ListenAndServe(":8080", wrapped)

// mount under a prefix
mux := http.NewServeMux()
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", api))

TypeScript Integration

The @shiftapi/vite-plugin extracts your OpenAPI spec at build time, generates TypeScript types via openapi-typescript, and serves a pre-configured openapi-fetch client as a virtual module.

Install:

npm install @shiftapi/vite-plugin

vite.config.ts:

import shiftapi from "@shiftapi/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
    plugins: [
        shiftapi({
            server: "./cmd/server", // Go entry point
        }),
    ],
});

Use the typed client:

import { client } from "@shiftapi/client";

const { data } = await client.GET("/health");
// data: { ok?: boolean }

const { data: greeting } = await client.POST("/greet", {
    body: { name: "frank" },
});
// body and response are fully typed from your Go structs

In dev mode the plugin also starts the Go server, proxies API requests through Vite, watches .go files, and hot-reloads the frontend when types change.

Plugin options:

Option Default Description
server (required) Go entry point (e.g. "./cmd/server")
baseUrl "/" Fallback base URL for the API client
goRoot process.cwd() Go module root directory
url "http://localhost:8080" Go server address for dev proxy

For production, set VITE_SHIFTAPI_BASE_URL in a .env.production file to point at your API host. The plugin automatically updates tsconfig.json with the required path mapping for IDE autocomplete.

Development

This is a pnpm + Turborepo monorepo.

pnpm install    # install dependencies
pnpm build      # build all packages
pnpm dev        # start example Vite + Go app
pnpm test       # run all tests

Go tests can also be run directly:

go test -count=1 ./...

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Connect

func Connect[Resp any](api *API, path string, fn HandlerFunc[Resp], options ...RouteOption)

Connect registers a CONNECT handler.

func Delete

func Delete[Resp any](api *API, path string, fn HandlerFunc[Resp], options ...RouteOption)

Delete registers a DELETE handler.

func Get

func Get[Resp any](api *API, path string, fn HandlerFunc[Resp], options ...RouteOption)

Get registers a GET handler.

func Head[Resp any](api *API, path string, fn HandlerFunc[Resp], options ...RouteOption)

Head registers a HEAD handler.

func ListenAndServe

func ListenAndServe(addr string, api *API) error

ListenAndServe starts the HTTP server on the given address.

In production builds this is a direct call to http.ListenAndServe with zero additional overhead.

When built with -tags shiftapidev (used automatically by the Vite plugin), the following environment variables are supported:

  • SHIFTAPI_EXPORT_SPEC=<path>: write the OpenAPI spec to the given file and exit without starting the server.
  • SHIFTAPI_PORT=<port>: override the port in addr, allowing the Vite plugin to automatically assign a free port.

func Options

func Options[Resp any](api *API, path string, fn HandlerFunc[Resp], options ...RouteOption)

Options registers an OPTIONS handler.

func Patch

func Patch[Body, Resp any](api *API, path string, fn HandlerFuncWithBody[Body, Resp], options ...RouteOption)

Patch registers a PATCH handler.

func Post

func Post[Body, Resp any](api *API, path string, fn HandlerFuncWithBody[Body, Resp], options ...RouteOption)

Post registers a POST handler.

func Put

func Put[Body, Resp any](api *API, path string, fn HandlerFuncWithBody[Body, Resp], options ...RouteOption)

Put registers a PUT handler.

func Trace

func Trace[Resp any](api *API, path string, fn HandlerFunc[Resp], options ...RouteOption)

Trace registers a TRACE handler.

Types

type API

type API struct {
	// contains filtered or unexported fields
}

API collects typed handler registrations, generates an OpenAPI schema, and implements http.Handler so it can be used with any standard server.

func New

func New(options ...Option) *API

New creates a new API with the given options.

func (*API) ServeHTTP

func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

type APIError

type APIError struct {
	Status  int    `json:"-"`
	Message string `json:"message"`
}

APIError is an error with an HTTP status code. Return it from handlers to control the response status code and message.

func Error

func Error(status int, message string) *APIError

Error creates a new APIError with the given status code and message.

func (*APIError) Error

func (e *APIError) Error() string

type Contact

type Contact struct {
	Name  string
	URL   string
	Email string
}

Contact describes the API contact information.

type ExternalDocs

type ExternalDocs struct {
	Description string
	URL         string
}

ExternalDocs links to external documentation.

type FieldError

type FieldError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

FieldError describes a single field validation failure.

type HandlerFunc

type HandlerFunc[Resp any] func(r *http.Request) (Resp, error)

HandlerFunc is a typed handler for methods without a request body (GET, DELETE, HEAD, etc.).

type HandlerFuncWithBody

type HandlerFuncWithBody[Body, Resp any] func(r *http.Request, body Body) (Resp, error)

HandlerFuncWithBody is a typed handler for methods with a request body (POST, PUT, PATCH, etc.).

type Info

type Info struct {
	Title          string
	Summary        string
	Description    string
	TermsOfService string
	Contact        *Contact
	License        *License
	Version        string
}

Info describes the API.

type License

type License struct {
	Name       string
	URL        string
	Identifier string
}

License describes the API license.

type Option

type Option func(*API)

Option configures an API.

func WithExternalDocs

func WithExternalDocs(docs ExternalDocs) Option

WithExternalDocs links to external documentation.

func WithInfo

func WithInfo(info Info) Option

WithInfo configures the API metadata.

func WithValidator

func WithValidator(v *validator.Validate) Option

WithValidator sets a custom validator instance on the API.

type RouteInfo

type RouteInfo struct {
	Summary     string
	Description string
	Tags        []string
}

RouteInfo provides metadata for a route in the OpenAPI spec.

type RouteOption

type RouteOption func(*routeConfig)

RouteOption configures a route.

func WithRouteInfo

func WithRouteInfo(info RouteInfo) RouteOption

WithRouteInfo sets the route's OpenAPI metadata.

func WithStatus

func WithStatus(status int) RouteOption

WithStatus sets the success HTTP status code for the route (default: 200).

type ValidationError

type ValidationError struct {
	Message string       `json:"message"`
	Errors  []FieldError `json:"errors"`
}

ValidationError is returned when request body validation fails.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Directories

Path Synopsis
examples
greeter command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL