Open-source project
fastschema/fastschema avatar
fastschema/fastschema

FastSchema: a Go backend framework where the schema drives the database and the API

All-in-One Backend as a Service with Headless CMS Power

576 stars56 forksGoMIT

At a glance

What is it?
FastSchema is a Backend as a Service and Go web framework that takes a content model and generates the database tables and the RESTful endpoints from it, so a headless CMS or an internal API can be built from a schema definition rather than from hand-written migrations and routers. It ships as a single static binary in a small Alpine image, and the README says plainly that it is in beta.
Who is it for?
Adopt FastSchema if you want a backend or a headless CMS defined by a schema rather than by hand-written migrations and routers, and if you are comfortable with a 0.x project that the README describes as in beta.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 104 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two products in one, and how the schema drives both

The README describes FastSchema as a Backend as a Service and Go web framework for building dynamic web applications, designed to simplify backend development, automating the generation of databases and providing ready-to-use CRUD APIs.

The mechanism is stated plainly in the Overview section. The core features are built on top of schema, a blueprint that outlines the structure of your content, and that schema acts as the foundation upon which FastSchema builds your database tables and API endpoints. So there is one artifact, the schema, and two things derived from it: the tables and the endpoints.

The second use case is using the same thing as a framework rather than as a platform. The first use case is a BaaS with headless CMS capabilities as a no-code solution, where you define content models and generate RESTful APIs effortlessly, and the README claims that with two lines of commands you can create a new project, define your content models and start creating content. The second is building custom web applications, with Resource as the core concept.

The features list names the same two outputs separately. Automated Database Generation means the tables are created from the schema definition with a flexible relationships model, eliminating manual setup. And RESTful API Generation means the endpoints follow the schema, with the promise that whenever you create or update a schema, the corresponding API endpoints are updated accordingly.

That last clause is the one to hold onto. If the endpoints really do follow the schema, then a content modelling tool and an API are the same activity, and the usual two-step workflow of defining a model and then writing handlers does not exist.

The primary language is Go, the licence is MIT, the default branch is develop, and the recent releases are v0.15.1 from 2026-07-11, v0.15.0 from 2026-07-10 and v0.14.0 from 2026-07-09. Three releases in three days tells you this is a fast-moving 0.x project, and the README states that FastSchema is currently in beta and under active development while still welcoming feedback and contributions.

Running it, and the two-minute path to a working backend

The try-it-out section is the shortest route to a running system, and it is a two-step Docker pull and run.

bash
docker pull ghcr.io/fastschema/fastschema:latest
docker run \
  -p 8000:8000 \
  -v ./data:/fastschema/data \
  ghcr.io/fastschema/fastschema:latest

The volume mount is the part to notice. Mapping ./data to /fastschema/data persists whatever the container writes, which given the startup output turns out to be both the database file and the generated application key.

The example output shows what the first run prints:

bash
> APP_KEY is not set. A new key is generated and saved to /fastschema/data/.env
> Using the default sqlite db file path: /fastschema/data/fastschema.db
> Visit the following URL to setup the app: http://localhost:8000/dash/setup/?token=lUDRgoTUUNDsjCcitgGFTqwMZQPmYvlU

Three things happen there. APP_KEY is not set, so a new one is generated and saved to /fastschema/data/.env. The default sqlite database file path is used, which is /fastschema/data/fastschema.db. And a setup URL is printed with a token in it.

That token is the security model for a fresh instance, and it is worth understanding before deploying. The first person to reach the setup URL with the correct token becomes the administrator. The README's own link is written with a placeholder and a note that the token is displayed in the terminal, so the intended flow is that you read the token from the container's output and paste it into your browser.

Two consequences follow. The container's stdout is a secret in a production deployment, so it should be captured somewhere with restricted access rather than left to a general log sink. And the setup page must be reachable only by you until it is completed, because an unauthenticated first visitor with the token becomes the owner of the backend.

The default database is sqlite on a local file, which is the right choice for a trial and not the right choice for anything with concurrent writes. The environment configuration shows that MySQL is the alternative, with driver, name, host, port, user and password as separate values.

Resources, hooks and the ORM: the framework side

The framework documentation is short and names three core concepts, which together are the extension surface of the whole system.

Resource is a core concept that represents an access point to your data, and by defining resources you can create custom endpoints and customise the behaviour of your APIs. That is the word customise doing work here, because the generated endpoints are the default and a resource is how you replace one.

Hooks are functions executed before or after an operation is performed on a resource. A Hook can be a resolver hook, a database hook or an application hook, and they allow you to add custom logic to your APIs and extend the functionality of FastSchema. The three-level distinction maps onto the three places you might want to intervene: at the resolver, where a request is turned into a response; at the database, where a query is run; and at the application, where something global happens.

The ORM is described as a powerful tool that simplifies the interaction with your database, providing a set of methods for querying, creating, updating and deleting records. The README's framework example shows it in use, and it is worth reading because the API shape tells you a lot about the design.

go
func main() {
	app, _ := fastschema.New(&fs.Config{
		SystemSchemas: []any{Tag{}, Blog{}},
	})

	app.API().Add(fs.Post("/blogvote", func(c fs.Context, vote *Payload) (*Response, error) {
		_, err := db.Mutation[Blog](app.DB()).
			Where(db.EQ("id", vote.ID)).
			Update(c.Context(), fs.Map{
				"$expr": fs.Map{"vote": "vote + 1"},
			})

		return &Response{
			Success: err == nil,
			Message: fmt.Sprintf("Vote for %d: %v", vote.ID, err),
		}, nil
	}))

	log.Fatal(app.Start())
}

Several details are worth pulling out. The framework is initialised with a config carrying SystemSchemas, a slice of any, which is how your own content models are registered with the system. The API is extended through app.API().Add, taking an fs.Post path and a handler with a typed payload and a typed response, so handlers are ordinary Go functions with generics rather than a bespoke interface. The database call is db.Mutation[Blog](app.DB()), which names the entity type as a type parameter, and the update is expressed as a map with an $expr key, meaning the framework accepts a MongoDB-style update expression rather than only field assignment. And the app.Start() call is what runs the server.

The typed handler signature is the most useful thing here. It means the generated API and the hand-written endpoints share one request and response path, and that a content model defined in Go is the same type used by the custom endpoint that mutates it.

The dependency list, and what it says about the design

The go.mod is long, and reading it is a faster way to understand the project than the README prose is.

The core is ent at 0.14.4, the entity framework, which is the schema-to-database layer: ent generates typed Go code from a schema definition, and FastSchema builds on that. The migration tooling is ariga.io/atlas at 0.36.1, Atlas being the schema migration engine that ent itself uses, and the presence of both together means FastSchema inherits a real migration story rather than inventing one. The database drivers are pgx for PostgreSQL, go-sql-driver/mysql for MySQL and ncruces/go-sqlite3 for the pure-Go SQLite default, which is why the container needs no C toolchain to run SQLite.

The HTTP layer is Fiber v2 on fasthttp rather than net/http. That is a performance choice with real consequences, since fasthttp keeps connection state and does not fit the standard net/http model, so middleware written for one does not drop into the other.

The expression engine is expr-lang/expr, which is what the $expr update syntax in the framework example runs on, and the schema language is HJSON through hjson-go, a relaxed JSON dialect, which suits a schema format intended to be edited by hand. The dashboard is a React application served by the same binary, and the repository has dash/ plus dash-version.txt, an embed test and jsconfig.json, which is a Go embed of a built frontend.

Then there are the dependencies that reveal optional subsystems. rclone at 1.70.3 is the storage driver behind the disks configuration, and ogen-go/ogen generates the OpenAPI client and server from a specification, and ogen is how the API is described formally. For authentication, golang-jwt/jwt for tokens, golang.org/x/oauth2 for the provider flows, and emersion/go-sasl plus emersion/go-smtp for the local email provider, with go-sasl being what the SMTP server needs for client authentication.

The rest is ordinary Go project furniture: testify for assertions, go-sqlmock for database tests, zap for logging, mapstructure for decoding, strcase for case conversion, inflection for pluralisation, uuid for identifiers, and the ordered-map package, which tells you something about the ordering guarantees the system cares about.

Configuration, and what the environment file tells you

The .env.example is more revealing than a configuration reference usually is, because it shows the shape of every subsystem.

The application block is small: an APP_KEY, an APP_ENV, an APP_PORT, an APP_BASE_URL, a separate APP_DASH_URL for the dashboard, and APP_API_BASE_NAME which is where the API is mounted. The database block has the driver, name, host, port, user, pass, a logging flag, and two flags that are worth pausing on. DB_DISABLE_FOREIGN_KEYS can turn off foreign key enforcement, and DB_USE_SOFT_DELETES can enable soft deletion. Both exist as switches, which means the underlying model supports them.

The storage block is JSON embedded in the environment variable, and it defines a list of disks. The default disk is local, with a root of ./public and a public_path of /. The second example is an S3 disk, and it is more interesting because it names a provider explicitly, DigitalOcean, with an endpoint of sfo3.digitaloceanspaces.com and a region of sfo3. So the storage layer is not just local files but any S3-compatible endpoint, with a base_url of a CDN in front of it.

The auth block lists enabled providers, github and google, and then configures local with an activation method of email and a verification method of link, plus a github provider with client id and secret, a google provider, and a twitter provider with consumer key and secret even though twitter is not in the enabled list. The local provider's activation_url and recovery_url fields are empty in the example, which suggests they are derived from APP_BASE_URL when left blank.

Then there are the token settings, which are the part to check before deploying. AUTH_ENABLE_REFRESH_TOKEN is false by default, AUTH_ACCESS_TOKEN_LIFETIME is 900 seconds, and AUTH_REFRESH_TOKEN_LIFETIME is 604800 seconds, which is a week. One-time passwords are disabled by default, with AUTH_OTP_LENGTH of 6, an expiration of 300 seconds and a maximum of 3 attempts. And MAX_REQUEST_BODY_SIZE is 4MB, which is a limit worth knowing about before uploading large files through the API.

Building, testing and the shape of the repository

The Makefile is small and tells you how the project is worked on.

bash
testpkg:
	./tests/test.sh ./pkg/...

test:
	./tests/test.sh

validate-example-schemas:
	@go run ./docs/schemas/cmd docs/schemas/
.PHONY: validate-example-schemas

lint:
	golangci-lint run

dev:
	mkdir -p ./data/tmp
	air -c ./.air.toml start .

There is a testpkg target that runs the test script over a single package, a test target that runs it over everything, and a validate-example-schemas target that runs a Go program over the schemas directory to validate the example schemas. That last target is a good sign, because it means the examples in the documentation are checked rather than merely written.

The lint target runs golangci-lint, and there is a .golangci.yml at the root. The dev target creates a data/tmp directory and runs air with the .air.toml configuration, so live reload is set up for the server. The .air.toml file at the top level is the standard air configuration, and the presence of a Makefile dev target plus an air config is the conventional Go live-reload setup.

The top-level listing shows the module structure, and a few names are worth noting. There is cmd/ for the binary entry point, db/ for the database layer, entity/ for the entity definitions, expr/ for the expression handling, fs/ for the framework core, schema/ for the schema machinery, services/ for the service layer, plugins/ for the plugin system, logger/, pkg/ and resources.go at the root. There is a dash/ directory for the dashboard and a dash-version.txt for the embedded frontend version.

The examples/ directory is the best place to look first, because its names map onto the concepts: database, hooks, logging, plugins, relations, resource and storage. Each is a small runnable program, and they cover exactly the extension points the framework section describes.

The build side is a GoReleaser setup, with a .goreleaser.yaml at the root, and the Dockerfile is a two-stage build that downloads a prebuilt release archive rather than compiling the source. The first stage is called fastschemadownloader, it takes the standard TARGETOS, TARGETARCH and TARGETVARIANT build arguments, and it fetches a zip named after the version and the platform from the GitHub releases. The second stage is alpine:3 with ca-certificates added, the binary copied in, port 8000 exposed, and the entrypoint /fastschema/fastschema start.

The consequence is that the Docker image does not build Go code at all. It fetches a release artefact, which makes the image small and the build fast, and it means the image content and the GitHub release are the same thing by construction.

How to evaluate it, and the questions to ask first

There are four things to establish before this goes anywhere near production.

First, the maturity signal. The project is at 0.15.1 with three releases in three days, the README says it is in beta, and the default branch is develop rather than main. The default branch name is worth noting on its own, because it means the published container tag and the branch you would normally treat as stable are not the same thing. Treat any deployment as needing its own pin.

Second, the schema-to-endpoint coupling, because it is the central promise and also the central constraint. If your API endpoints follow your schema automatically, then changing a content model is an API change, and you need to know what the framework does about existing clients when a field is removed rather than deprecated. The README does not answer that, so it is worth testing with a throwaway project before you commit a model to it.

Third, the database choice. The default is SQLite on a local file, which is fine for a trial and for a single-node internal tool. The drivers are also available for MySQL and PostgreSQL, and the migration tooling comes from Atlas, which is a well-trodden path, so moving up is a configuration change rather than a rewrite. But the soft delete and foreign key switches are set to false in the example configuration, and those defaults are a starting point rather than a recommendation.

Fourth, the operational surface. There is an auth block with JWT token lifetimes, one-time passwords and a set of OAuth providers, a storage block that can point at S3-compatible object storage, and an OpenAPI layer generated with ogen. That is a substantial surface, and it means the security-relevant configuration is substantial too. In particular, the setup token printed at first run is the thing to protect, since whoever completes setup becomes the owner.

The honest summary is that this is a well-organised Go project with a clear idea, a real migration story, and honest documentation of what it is and is not, currently in beta. If you want to spin up a headless CMS or an internal API without writing migrations and routers, it is worth a trial on a spare machine. If you need to know exactly what it will do in three years, it is too early to answer that from the repository.

Editorial conclusion

Adopt FastSchema if you want a backend or a headless CMS defined by a schema rather than by hand-written migrations and routers, and if you are comfortable with a 0.x project that the README describes as in beta. Do not adopt it if you need a stable API contract that survives content model changes without review, because the generated endpoints follow the schema and the repository does not document the compatibility behaviour, and do not run it against data you care about until you have checked the setup token flow and the auth configuration. Before you commit, pin the container version rather than tracking latest, set the database to MySQL or PostgreSQL with soft deletes and foreign keys switched on for anything real, and work through the examples directory, which covers database, hooks, logging, plugins, relations, resource and storage and is the fastest way to see what the framework does.

Frequently asked questions

What is FastSchema?

It is a Backend as a Service and Go web framework for building dynamic web applications. It automates database generation, provides ready-to-use CRUD APIs, and is built on top of a schema blueprint that defines both the database tables and the API endpoints derived from it.

How do I run FastSchema with Docker?

Pull ghcr.io/fastschema/fastschema:latest and run it with port 8000 mapped and a ./data volume mounted at /fastschema/data. On first start it generates and saves an APP_KEY to /fastschema/data/.env, uses the default SQLite file at /fastschema/data/fastschema.db, and prints a setup URL containing a token that you use to open the setup page.

What are Resources and Hooks in FastSchema?

A Resource is an access point to your data, and defining one lets you create custom endpoints and customise the behaviour of the generated APIs. Hooks are functions executed before or after an operation on a resource, and they come in three kinds: resolver hooks, database hooks and application hooks.

Which databases does FastSchema support?

The go.mod lists drivers for MySQL, PostgreSQL through pgx, and SQLite through a pure-Go driver, with SQLite on a local file as the default. The example environment file also shows DB_DISABLE_FOREIGN_KEYS and DB_USE_SOFT_DELETES switches, and the migration layer is built on ariga.io/atlas with ent for entity generation.

What does the FastSchema configuration include?

The example environment file covers application settings such as APP_KEY, APP_PORT and APP_API_BASE_NAME, the database block, a JSON storage block defining local and S3-compatible disks, an auth block listing local, github, google and twitter providers with token lifetimes and one-time password settings, and a MAX_REQUEST_BODY_SIZE limit of 4MB.

Is FastSchema ready for production use?

The README states that FastSchema is currently in beta and under active development while welcoming feedback and contributions. The recent releases are v0.15.1, v0.15.0 and v0.14.0 in three consecutive days, and the default branch is develop, so any deployment should pin its own version.

Official sources

  1. fastschema/fastschema on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/fastschema-fastschema.svg)](https://hysenlabs.com/projects/fastschema-fastschema)

Was this useful?