This document explains the high-level architecture of the memos application, covering the three-tier architecture (frontend, backend, data layer), the single-binary deployment model, and how the Go backend integrates with the React frontend. For details on specific data models and storage patterns, see Data Models and Storage. For API service design and gRPC implementation, see API and Service Architecture.
Memos is built as a three-tier monolithic application with a clear separation between the frontend presentation layer, backend business logic layer, and data persistence layer. Despite being architecturally separated, these tiers are deployed as a single binary, making deployment and operation extremely simple.
High-Level Architecture
Sources:
The frontend is a React Single Page Application (SPA) built with Vite, using modern React patterns including hooks, context providers, and functional components. All frontend assets are compiled into static files (HTML, JavaScript, CSS) during the build process.
Key Technologies:
| Technology | Purpose | Package |
|---|---|---|
| React 18.3 | UI framework | [email protected] |
| Vite 7.2 | Build tool and dev server | [email protected] |
| React Router 7.9 | Client-side routing | [email protected] |
| Connect RPC | Type-safe gRPC-web client | @connectrpc/[email protected] |
| TanStack Query 5.90 | Server state management | @tanstack/[email protected] |
| Tailwind CSS 4.1 | Styling framework | [email protected] |
| i18next 25.6 | Internationalization | [email protected] |
Build Process:
The frontend is built using two npm scripts defined in package.json:
npm run dev - Starts Vite dev server with HMRnpm run release - Builds with optimizations and outputs to ../server/router/frontend/distThe --outDir flag ensures the compiled frontend assets are placed directly in the Go project structure, where they will be embedded into the binary.
Sources:
The backend is written in Go using the Echo v5 HTTP framework. It provides both HTTP/JSON APIs (via gRPC Gateway) and native gRPC endpoints. The core server is defined in the Server struct.
Server Struct:
Middleware Stack:
The Echo server uses minimal middleware to keep the request path fast:
Route Registration Order:
Routes are registered in a specific order to ensure correct request handling:
GET /healthz) - Always responds first/o/r/*) - Registered before gRPC Gateway for Safari range request support/explore/rss.xml) - Public RSS endpoint/api/v1/*) - All API endpointsSources:
The data layer uses a store abstraction pattern that provides a unified interface for database operations. This allows memos to support three different database backends without changing application code.
Store Interface Pattern:
store.Store (interface)
├── SQLite Driver (modernc.org/sqlite)
├── MySQL Driver (go-sql-driver/mysql)
└── PostgreSQL Driver (lib/pq)
Each driver implements database-specific SQL syntax:
RETURNING clause for insert operationsLastInsertId() for retrieving auto-increment IDs$1, $2 placeholder syntaxDatabase Selection:
The database is selected at startup based on the --driver flag or MEMOS_DRIVER environment variable. All three drivers are compiled into the binary, and the appropriate one is initialized at runtime.
Sources:
One of memos' key architectural decisions is single-binary deployment: the entire application (frontend assets, backend code, all database drivers) is compiled into a single executable binary. This dramatically simplifies deployment and eliminates dependency management issues.
Single Binary Components
During the build process:
Frontend Build (npm run release):
server/router/frontend/distGo Embedding (using //go:embed directive):
dist directory in the binaryembed.FS at runtimeServing Embedded Assets:
FrontendService serves files from the embedded filesystemindex.html for client-side routingBuild Script:
The release build is configured in package.json:
This ensures frontend assets are placed in the correct location for Go embedding.
Distribution:
Users receive a single binary file:
memos-linux-amd64 (~30-40 MB)memos-darwin-arm64 (~30-40 MB)memos-windows-amd64.exe (~30-40 MB)No Node.js, no npm, no separate web server - just one binary.
Sources:
The frontend and backend communicate using gRPC-web over HTTP, with all API definitions specified in Protocol Buffers for type safety across the language boundary.
Communication Architecture
All API contracts are defined using Protocol Buffers (protobuf), providing:
Code Generation Flow:
.proto filesprotoc)Frontend uses Connect for Web (@connectrpc/connect-web), which implements the gRPC-web protocol:
application/connect+json or application/jsonClient-Side Example:
On the backend, grpc-gateway translates HTTP/JSON requests to gRPC calls:
POST /api/v1/memos with JSON bodyMemoService.CreateMemo(request)This allows the backend to expose both:
Sources:
The store.Store interface provides complete database abstraction, allowing the same application code to run on SQLite, MySQL, or PostgreSQL without modification.
Benefits:
Implementation:
Each database driver implements the same interface methods:
CreateMemo(ctx, memo) (*Memo, error)GetMemo(ctx, memoID) (*Memo, error)ListMemos(ctx, filter) ([]*Memo, error)UpdateMemo(ctx, update) (*Memo, error)DeleteMemo(ctx, memoID) errorDatabase-specific SQL is isolated in driver implementations.
Database Configuration:
Selected via environment variable or command-line flag:
All three drivers are compiled into the binary; only one is initialized.
Sources:
Echo middleware provides cross-cutting concerns like recovery, logging, and authentication. The stack is kept minimal for performance.
Core Middleware:
Additional middleware is added conditionally based on configuration:
Sources:
Background runners perform asynchronous tasks without blocking HTTP requests. Each runner runs in its own goroutine with independent cancellation.
Current Runners:
| Runner | Purpose | Interval |
|---|---|---|
s3presign.Runner | Generate presigned URLs for S3 resources | Periodic |
Runner Lifecycle:
StartBackgroundRunners()context.WithCancel()runnerCancelFuncsGraceful Shutdown:
When Server.Shutdown() is called:
Sources:
Server Initialization and Lifecycle
Key Steps:
profile.ProfileSources:
The server is configured via the profile.Profile struct, which reads from environment variables and command-line flags:
Key Configuration Options:
| Field | Type | Purpose | Default |
|---|---|---|---|
Mode | string | Development or production | production |
Addr | string | Listen address | 0.0.0.0 |
Port | int | HTTP port | 5230 |
UNIXSock | string | Unix socket path (alternative to TCP) | "" |
Data | string | Data directory for SQLite | ~/.memos |
Driver | string | Database driver (sqlite/mysql/postgres) | sqlite |
DSN | string | Database connection string | (generated) |
Demo | bool | Demo mode (uses test credentials) | false |
Example Configurations:
Sources:
Refresh this wiki