Principles
Five principles guide every decision in Nucleus. This page explains what they are and why they were chosen — read it to understand the reasoning behind the framework's shape. The rest of the documentation is where they show up in practice.
1. Stdlib-first runtime
The runtime is built on net/http, database/sql, log/slog, and
context.Context. Every new third-party dependency has to be argued for in
writing and reviewed for its impact before it is taken.
The reasoning is operational, not aesthetic:
- The standard library is the most reviewed Go code in existence; its semantics rarely surprise.
- Third-party deps are the dominant source of maintenance debt and supply-chain risk.
- Go's release cadence is predictable; building on the stdlib lets us adopt new features without coordinating across vendors.
When a dependency is genuinely worth taking — Asynq for the task queue, Casbin for RBAC, OpenTelemetry for tracing — it goes behind a framework interface, so that it stays replaceable and never appears in a signature you depend on.
2. Explicit configuration & lifecycle
There are no hidden global singletons, no implicit init() registration,
no package-level state masquerading as the framework. Every Nucleus
application is created by an explicit call:
a, err := app.New(cfg, opts...)
The implications:
- Multiple
Appinstances coexist trivially in the same process — end-to-end tests run a realApp, not a mock. - Lifecycle is observable.
App.Runblocks;App.Shutdownruns hooks in reverse order;defer a.Shutdown(ctx)is enough. - Wiring is reviewable. The composition root is the project's
main.go(or a fluent builder call); nothing happens at import time.
3. Compatibility by contract
The stable surface is explicit:
- exported symbols in
pkg/*, - registered CLI commands,
- registered config keys,
- the framework services an extension may reach on
app.App.
Each of these is frozen by tests under
contracts/.
Removals require a deprecation entry; rename-and-keep-the-shim is the
default path. Details: Compatibility policy.
That last one is newer than the rest, and it exists because of what it replaced. An extension receives the whole application object, and the contract used to say it could set fields on it. That was a blank cheque: whatever an extension reached for became part of the API in practice, while being covered by nothing anyone could promise across versions. No extension ever used the permission, so it cost stability and bought nothing.
An extension now reads framework services, mounts routes and registers middleware. What it may read is frozen like everything else, so adding to it is a deliberate promise and removing from it is a break somebody has to see — which is the difference between having extension points and having an ecosystem.
4. Security by default
Production-sensitive defaults ship enabled, so relaxing one is a deliberate, visible act rather than a missing line:
- Session cookies are
Secureby default (session_cookie_secure: true); plain-HTTP local development must opt out explicitly. - CORS denies unknown origins until
cors_originslists them. - RBAC is default-deny: a route with no matching policy answers 403.
- Proxy headers such as
X-Forwarded-Forare ignored untiltrusted_proxiesnames the peers you trust. - A hardened set of security headers is applied to every response.
- Passwords are hashed with bcrypt at cost 12.
- Under
NUCLEUS_ENV=production, an unknown config key is a boot error.
Two protections are deliberately opt-in, because switching them on
unconditionally would break valid deployments: CSRF (csrf_enabled, which
only applies to cookie-authenticated browser routes) and rate limiting
(rate_limit_requests).
Every setting is reachable from nucleus.yml. If you need to relax a
default, you do it deliberately, in writing.
5. SQL-first operations
There is no ORM. Migrations are SQL files; the CLI applies them in
order; queries are written in SQL. pkg/db adds database/sql
ergonomics, telemetry and health checks; pkg/model adds metadata for
the admin panel and CRUD helpers.
This is a constraint we want, not one we tolerate:
- Postgres, MySQL and SQLite all behave differently in subtle ways.
MSSQL and Oracle add their own dialects on top (opt-in via the
-tags mssql/-tags oraclebuild tags). Hiding any of this behind an ORM either produces the lowest-common-denominator query or quietly emits incompatible SQL. - Migrations as SQL files are reviewable and replayable independently
of the binary that wrote them. Oracle multi-block PL/SQL scripts are
split correctly by
db.ExecScriptso the slash-terminator works as developers expect (see Models & database → Multi-block scripts). - The CLI is deterministic: the same
nucleus migrateagainst the same database ends in the same state.nucleus migrate driftreports divergence between the live schema and the registered models on every supported engine.
What follows from the principles
- The compatibility SLO has bite — see Compatibility policy.
- The CLI is a first-class product, not an afterthought — see CLI reference.
- The admin panel is the orbit module, a pluggable product that mounts into any Nucleus app — see Features → Admin panel.