shiftapi

package module
v0.0.17 Latest Latest
Warning

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

Go to latest
Published: Feb 28, 2026 License: MIT Imports: 16 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 or Next.js 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 shiftapi

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, Svelte, or Next.js):

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, in *Person) (*Greeting, error) {
    return &Greeting{Hello: in.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. Every method uses a single function — struct tags discriminate query params (query:"...") from body fields (json:"..."). For routes without input, use _ struct{}.

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

// GET without input — use _ struct{}
shiftapi.Get(api, "/users/{id}", func(r *http.Request, _ struct{}) (*User, error) {
    return db.GetUser(r.Context(), r.PathValue("id"))
})

Typed query parameters

Define a struct with query tags. Query params are parsed, validated, and documented in the OpenAPI spec automatically.

type SearchQuery struct {
    Q     string `query:"q"     validate:"required"`
    Page  int    `query:"page"  validate:"min=1"`
    Limit int    `query:"limit" validate:"min=1,max=100"`
}

shiftapi.Get(api, "/search", func(r *http.Request, in SearchQuery) (*Results, error) {
    return doSearch(in.Q, in.Page, in.Limit), nil
})

Supports string, bool, int*, uint*, float* scalars, *T pointers for optional params, and []T slices for repeated params (e.g. ?tag=a&tag=b). Parse errors return 400; validation failures return 422.

For handlers that need both query parameters and a request body, combine them in a single struct — fields with query tags become query params, fields with json tags become the body:

type CreateInput struct {
    DryRun bool   `query:"dry_run"`
    Name   string `json:"name"`
}

shiftapi.Post(api, "/items", func(r *http.Request, in CreateInput) (*Result, error) {
    return createItem(in.Name, in.DryRun), nil
})

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

ShiftAPI ships npm packages for the frontend:

  • shiftapi — CLI and codegen core. Extracts the OpenAPI spec from your Go server, generates TypeScript types via openapi-typescript, and writes a pre-configured openapi-fetch client.
  • @shiftapi/vite-plugin — Vite plugin for dev-time HMR, proxy, and Go server management.
  • @shiftapi/next — Next.js integration with the same DX (webpack/Turbopack aliases, rewrites proxy, Go server management).

shiftapi.config.ts (project root):

import { defineConfig } from "shiftapi";

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

Vite

npm install shiftapi @shiftapi/vite-plugin
// vite.config.ts
import shiftapi from "@shiftapi/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
    plugins: [shiftapi()],
});

Next.js

npm install shiftapi @shiftapi/next
// next.config.ts
import type { NextConfig } from "next";
import { withShiftAPI } from "@shiftapi/next";

const nextConfig: NextConfig = {};

export default withShiftAPI(nextConfig);

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

const { data: results } = await client.GET("/search", {
    params: { query: { q: "hello", page: 1, limit: 10 } },
});
// query params are fully typed too — { q: string, page?: number, limit?: number }

In dev mode the plugins start the Go server, proxy API requests, watch .go files, and regenerate types on changes.

CLI usage (without Vite/Next.js):

shiftapi prepare

This extracts the spec and generates .shiftapi/client.d.ts and .shiftapi/client.js. Useful in postinstall scripts or CI.

Config options:

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

For production, set VITE_SHIFTAPI_BASE_URL (Vite) or NEXT_PUBLIC_SHIFTAPI_BASE_URL (Next.js) to point at your API host. The plugins automatically update 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 -tags shiftapidev ./...

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Connect

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

Connect registers a CONNECT handler.

func Delete

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

Delete registers a DELETE handler.

func Get

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

Get registers a GET handler.

func Head[In, Resp any](api *API, path string, fn HandlerFunc[In, 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[In, Resp any](api *API, path string, fn HandlerFunc[In, Resp], options ...RouteOption)

Options registers an OPTIONS handler.

func Patch

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

Patch registers a PATCH handler.

func Post

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

Post registers a POST handler.

func Put

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

Put registers a PUT handler.

func Trace

func Trace[In, Resp any](api *API, path string, fn HandlerFunc[In, 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[In, Resp any] func(r *http.Request, in In) (Resp, error)

HandlerFunc is a typed handler for routes. The In struct's fields are discriminated by struct tags: fields with `query:"..."` tags are parsed from query parameters, and fields with `json:"..."` tags (or no query tag) are parsed from the request body. For routes without input, use struct{} as the In type.

type Info

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

Info describes the API.

type License

type License struct {
	Name string
	URL  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