A research & development project mirroring the official Google Keep REST API — built with Goa (Go), Next.js 16, and PostgreSQL.
| Feature | Status |
|---|---|
| 📝 Create, edit, delete notes | ✅ |
| 📋 Checklist / list notes | ✅ |
| 🎨 Solid color backgrounds (10 colours) | ✅ |
| 🌈 Gradient theme backgrounds (8 themes) | ✅ |
| 📌 Pin / unpin notes | ✅ |
| 🗂️ Archive / unarchive notes | ✅ |
| 🗑️ Trash / restore / permanent delete | ✅ |
| 🏷️ Label management (CRUD + rename) | ✅ |
| 🔍 Full-text search (ILIKE) | ✅ |
| 🌙 Dark mode (persisted to localStorage) | ✅ |
| 📎 Attachment upload & download | ✅ |
| 🔗 Share permissions (batch create/delete) | ✅ |
| 🧭 Sidebar navigation (collapsible mini mode) | ✅ |
| ☸️ Helm chart + ArgoCD deployment | ✅ |
| 🏠 Kubernetes deployment (with OAuth2 proxy) | ✅ |
| 📖 OpenAPI 3.0 docs (Swagger UI) | ✅ |
Backend
- Go 1.25 with Goa v3.28 (design-first API framework)
- PostgreSQL 16 with pgx/v5 connection pool
- golang-migrate/v4 for schema migrations
- Goa CLUE for structured logging & debugging
- go:embed for OpenAPI spec bundling
Frontend
- Next.js 16 (App Router, standalone output)
- React 19 with TypeScript 5
- Tailwind CSS v4 (no config file —
@import "tailwindcss"inglobals.css) - Lucide React icons
- clsx for conditional classNames
Infrastructure
- Docker Compose (local dev)
- Helm chart (recommended deploy)
- ArgoCD GitOps deployment
- Kubernetes manifests (legacy)
- OAuth2 Proxy with Google provider
- GitHub Actions CI (lint → build → test → Docker push)
.
├── backend/ # Go module: github.com/flaccid/google-keep-clone/backend
│ ├── design/ # Goa DSL — edit these first
│ │ ├── design.go # API metadata & server config
│ │ ├── types.go # Data types (Note, Section, ListItem, etc.)
│ │ ├── notes.go # Notes service (14 methods)
│ │ ├── labels.go # Labels service (CRUD + update)
│ │ ├── permissions.go # Permissions service (batch create/delete)
│ │ └── media.go # Media service (download)
│ ├── gen/ # 🔒 Auto-generated Goa code — never edit
│ ├── api/ # Business logic
│ │ ├── notes.go
│ │ ├── labels.go
│ │ ├── permissions.go
│ │ └── media.go
│ ├── store/ # PostgreSQL data layer
│ │ ├── db.go # pgx/v5 connection pool
│ │ ├── notes.go # Note CRUD, search, label join
│ │ ├── labels.go # Label CRUD
│ │ ├── permissions.go # Permission queries
│ │ ├── attachments.go # File-based attachment store
│ │ ├── migrations/ # SQL migration files
│ │ ├── migrate.go # Auto-migration runner
│ │ ├── notes_test.go # Integration tests (17 tests)
│ │ ├── labels_test.go # Integration tests (3 tests)
│ │ └── permissions_test.go # Integration tests (5 tests)
│ ├── cmd/keep_server/ # Server entrypoint
│ │ ├── main.go # Flags, DB init, service wiring
│ │ ├── http.go # HTTP server, upload handler, OpenAPI handler
│ │ └── openapi3.yaml # 📎 Embedded OpenAPI spec
│ ├── Makefile # `make gen` → goa gen + spec copy
│ ├── Dockerfile # Multi-stage Go build → alpine
│ ├── go.mod # Go 1.25.0
│ └── go.sum
├── frontend/ # Next.js 16 (App Router, TypeScript, Tailwind v4)
│ ├── src/
│ │ ├── app/ # App Router pages
│ │ │ ├── page.tsx # Home / note grid
│ │ │ ├── archive/page.tsx # Archived notes
│ │ │ ├── trash/page.tsx # Trashed notes
│ │ │ ├── reminders/page.tsx # Reminders (placeholder)
│ │ │ └── labels/
│ │ │ ├── page.tsx # Label manager (inline rename/delete)
│ │ │ └── [id]/page.tsx # Notes filtered by label
│ │ ├── components/ # React components
│ │ │ ├── Header.tsx # Fixed header with lightbulb icon
│ │ │ ├── Sidebar.tsx # Collapsible sidebar (mini mode)
│ │ │ ├── NoteEditor.tsx # Inline "Take a note..." + label picker
│ │ │ ├── NoteCard.tsx # Note display card + color/theme palette
│ │ │ ├── NoteModal.tsx # Full-note overlay editor
│ │ │ ├── Palette.tsx # Shared color/theme picker
│ │ │ └── ThemeProvider.tsx # Dark mode context + localStorage
│ │ └── lib/
│ │ ├── api.ts # Full API client (notes, labels, permissions)
│ │ └── types.ts # TypeScript interfaces
│ ├── next.config.ts # Rewrites: /v1/* → backend
│ ├── Dockerfile # Multi-stage Next.js standalone
│ └── package.json # Next.js 16, React 19, Tailwind v4
├── charts/ # Helm chart (recommended deploy)
├── k8s/ # Kubernetes manifests (legacy)
├── docs/ # getting-started.md, api-reference.md
├── docker-compose.yml # postgres + backend + frontend
└── .github/workflows/ci.yml # Lint → Build → Test → Docker push
See the full API reference for all endpoints, query parameters, note body types, colour values, and usage examples. Interactive docs are also available at /openapi (Swagger UI) when running the app.
See the getting-started guide for cluster setup, OAuth2 configuration, Helm install, and ArgoCD deployment.
docker compose upThis starts three services:
- PostgreSQL 16 on port
5432 - Backend on port
8080(auto-migrates DB on startup) - Frontend on port
3000
Visit http://localhost:3000 — all API calls are proxied from the Next.js dev server to the Go backend.
# Terminal 1 — Database
docker run -d --name keep-pg \
-e POSTGRES_USER=keep \
-e POSTGRES_PASSWORD=keep \
-e POSTGRES_DB=keep \
-p 5432:5432 \
postgres:16-alpine
# Terminal 2 — Backend
cd backend
DATABASE_URL="postgres://keep:keep@localhost:5432/keep?sslmode=disable" \
go run ./cmd/keep_server -host=localhost
# Terminal 3 — Frontend
cd frontend
npm install
npm run devcd backend
# 1. Edit the DSL in design/*.go
# 2. Regenerate Goa code + copy OpenAPI spec
make gen
# 3. Update business logic in api/*.go
# 4. Build & verify
go build ./...
go vet ./...
# 5. Run store integration tests (requires PostgreSQL)
DATABASE_URL="postgres://keep:keep@localhost:5432/keep?sslmode=disable" \
go test -count=1 ./store/... -vRules:
gen/is auto-generated — never edit manually- Route params use
mux.Vars(r)(Goa muxer), notr.PathValue - The DB auto-migrates on startup — no manual migration step needed
- After
make gen, always synccmd/keep_server/openapi3.yaml(already done by the Makefile)
cd frontend
npm install
npm run dev # Development server on :3000
npm run build # TypeScript check + production build
npm run lint # ESLintKey details:
- API calls use relative paths (
/v1/notes, etc.) — CORS is not needed - Next.js rewrites proxy
/v1/*,/openapi,/openapi.yamlto the backend API_UPSTREAM_URLis a build-time ARG baked into rewrites, not a runtime env var- Tailwind v4 is configured via
@import "tailwindcss"+@theme {}inglobals.css— notailwind.config.tsorpostcss.config.ts
The recommended way is via the Helm chart — see the getting-started guide for complete instructions with Helm CLI or ArgoCD.
You can also apply the raw k8s/ manifests (legacy) — note these lack the chart's value overrides, secret generation, and network policy egress rules.
Components (all in namespace google-keep-clone):
| Component | Type | Details |
|---|---|---|
| PostgreSQL | StatefulSet + Service | PersistentVolumeClaim, init container wait |
| Backend | Deployment + Service + HPA | 100m CPU / 128Mi requests, auto-migration |
| Frontend | Deployment + Service | Standalone Next.js output |
| OAuth2 Proxy | Deployment + Service | Google OAuth, email whitelist |
| Ingress | 2 Ingress resources | Main (auth-protected) + /oauth2 (unauthenticated) |
GitHub Actions runs on every push/PR to main:
- Lint —
go vet ./...+govulncheck(continue-on-error) - Build & Test —
go build ./...+ store integration tests (with PostgreSQL container) - Frontend —
npm ci+npm audit --audit-level=moderate(continue-on-error) +npm run build - Docker push — On
mainonly, pushesflaccid/google-keep-clone-{backend,frontend}:latestand:{sha}
- Sidebar: Collapsed = icons-only 68px strip; hover temporarily expands with shadow overlay; hamburger toggles expanded/collapsed; main content margin adjusts accordingly
- Color palette: Click-to-toggle dropdown with two sections — Themes (gradient swatches) and Colors (solid circles)
- Dark mode: Toggle in settings dropdown, persists to localStorage, class-based via Tailwind
dark:variant - Note editor: Inline "Take a note..." with expand, text/list toggle, label picker, color/theme palette
- Label manager: Inline rename (pencil icon), delete with confirmation, sidebar "Edit labels" link
- Labels on notes:
Note.labelscontains display name strings (not resource names); label filter page fetches all labels and matches by displayName
This is an R&D project — authentication is not yet implemented. The API currently has no user isolation: anyone who can reach the backend can read, create, modify, or delete any note. The database schema includes a permissions table for future multi-user support, but no access control logic exists yet.
For local development with Docker Compose, the backend is only accessible via the Next.js proxy on localhost:3000. For Kubernetes deployments, OAuth2 proxy provides authentication at the ingress level (see Kubernetes Deployment).
The Go backend serves plain HTTP on port 8080. In Kubernetes deployments, TLS is terminated at the ingress (nginx-ingress with OAuth2 proxy). In local development with Docker Compose, traffic stays on localhost. The backend does not serve HTTPS directly.
Security vulnerabilities are tracked via CI tooling (npm audit, govulncheck) and SECURITY.md (kept local, not tracked in git).
| Table | Purpose |
|---|---|
notes |
Core note data (title, body_text, color, pinned, archived, trashed) |
list_items |
Checklist items (linked to notes, ordered, with checked state) |
labels |
Label definitions (name, hex color) |
note_labels |
Many-to-many join between notes and labels |
permissions |
Note sharing (email, role, type) |
attachments |
File metadata (filename, size, mime type, linked to notes) |
Migrations live in backend/store/migrations/ and are embedded into the binary via //go:embed.
This is a personal R&D project, but issues and PRs are welcome! Feel free to open a discussion for major changes first.
- Author: Chris Fordham (chris@example.com)
Copyright 2026, Chris Fordham
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.