Overview
Quick notes tend to end up in scratch files, chat threads and an untrusted note app, with no account protection and nothing that can be audited. Bridopen is a single, self-hosted place to write, tag, attach files to and archive notes, aimed at individuals and small teams that would rather run their own instance than hand the content to a third party. It is a personal engineering project, published open source so the decisions behind it are inspectable rather than asserted.
My contribution
I am the sole author of the repository. I designed the layering, wrote the HTTP handlers, services, domain models and repositories, the SQL migrations, the HTML templates and the browser JavaScript, the security middleware, and the test suite. I also wrote the Docker Compose setups, the Makefile targets and the documentation.
The project exists to practise end-to-end engineering decisions — boundaries between layers, session and authorization handling, upload safety, migrations and release versioning — in a codebase I control completely and can therefore publish in full.
Architecture
A modular monolith in layers. cmd/http is the composition root: it loads configuration,
opens the database pool, wires the session manager, mailer, storage, scanner, repositories, services and
handlers, then registers the routes. Business modules live under internal/handlers
(notes, users, legal); cross-cutting infrastructure lives under
internal/platform.
cmd/http/routes.go, cmd/http/security.go and
internal/platform/middleware.
Layer rules
- Handlers deal with HTTP only: session, request parsing, response, rendering and JSON.
- Services hold use cases, flow validation and orchestration.
- Models hold entities, states, constants and pure domain rules.
- Repositories isolate SQL, row scanning, transactions and persistence mapping.
- Templates receive DTOs and page models, never business rules.
Stack
- Backend: Go 1.27 with the standard
net/httprouter; no web framework. - Data: PostgreSQL via
pgx/v5; schema managed bygolang-migrate. - Sessions:
alexedwards/scswith the PostgreSQL store. - Views:
html/templaterendered server-side, Tailwind CSS and plain JavaScript split by responsibility. - Delivery: Docker Compose for development and production, Caddy in front, Git tags as the version of record.
Engineering decisions
Three decisions that shaped the codebase, each with the alternative considered and what it costs.
1. Standard library HTTP instead of a web framework
- Decision:
- routing, middleware and request handling are built on
net/httpalone, using the method-and-path patterns available in the standardServeMux. - Alternative:
- Gin, Echo or Chi, which would have supplied routing groups, binding and a middleware stack out of the box.
- Reason:
- a small dependency surface, no framework idioms leaking into handlers, and layer boundaries that have to be stated explicitly rather than inherited.
- Limitation:
- the middleware chain, the error mapping and the request helpers are hand-written, so there is more of my own code to maintain and test than a framework would require.
2. Server-side rendering instead of an SPA
- Decision:
- pages are rendered with
html/templateand progressively enhanced with plain JavaScript; there is no client framework and no bundler. - Alternative:
- a JSON API with a React or Vue front end, deployed separately.
- Reason:
- one deployable artefact, state kept on the server with the session, and no build pipeline to keep in sync with the backend.
- Limitation:
- richer client interactions have to be written by hand. Tailwind is currently loaded from a CDN at runtime, which forces
script-src 'unsafe-inline'in the Content-Security-Policy; compiling the stylesheet at build time and tightening that directive is an open item.
3. Database-backed sessions instead of stateless tokens
- Decision:
- sessions are stored in PostgreSQL through
scswith the pgx store, and a separate table records the user's active sessions. - Alternative:
- a signed, stateless token (JWT) in a cookie, validated without touching the database.
- Reason:
- a signed-in user can list their active sessions and revoke them from the account page, and signing out actually invalidates the session server-side rather than waiting for a token to expire.
- Limitation:
- every authenticated request performs a session lookup against the database, so the database is on the critical path for all authenticated traffic.
Quality and security
Everything below is present in the public repository and can be checked against the source. Nothing here is a claim about how secure a deployed instance is — that depends on how it is configured and operated.
Tests and CI
- 26 test files across handlers, services, repositories, models, DTOs and the platform packages.
- Written with
testify; repository tests usepgxmock/v4so SQL and scanning are exercised without a live database. - GitHub Actions runs
go vet ./...andgo test ./...on every push tomainand every pull request. - Reproduce locally with
go test -count=1 ./....
Access controls
- Passwords hashed with bcrypt.
- TOTP two-factor authentication (6 digits, 30-second period, one-period validation window) with QR enrolment and recovery codes; the shared secret is encrypted at rest.
- CSRF tokens via
gorilla/csrf, secure cookie flags, and a captcha on the authentication forms. - Per-IP rate limiting and security headers —
Content-Security-Policy,X-Frame-Options: DENY,X-Content-Type-Options,Referrer-Policy,Permissions-Policy.
Data and operations
- Seven versioned SQL migrations under
db/migrations, applied withgolang-migrate. - Business events recorded in a dedicated audit schema.
- Attachments written to a volume outside the database; when a ClamAV address is configured, uploads are scanned before being persisted.
- Account export and account deletion available to the signed-in user from the profile page.
- Releases tagged
vMAJOR.MINOR.PATCH; the running build reports version, commit and build time atGET /version.
Results and limitations
What exists today is a working application: sign-up with email confirmation, sign-in with optional two-factor
authentication, password recovery, notes with tags, attachments, archive and trash with reversible deletion,
light and dark themes, account export and deletion, and public legal pages. It runs from a single
docker compose setup with migrations applied by a Make target.
Stated limitations
- No performance benchmark has been run. I have no measured latency, throughput or uptime figures for this project, so none are claimed.
- No official hosted instance. There is no public demo to link to. Anyone who deploys Bridopen is responsible for the service, its users' data and the legal texts on the public pages.
-
Attachment scanning is opt-in. When
BRIDOPEN_CLAMAV_ADDRis not configured, the application falls back to a no-op scanner and uploads are stored without being scanned. - The legal pages are generic templates. They need legal review before any real deployment.
- The Content-Security-Policy is not as strict as it could be. Loading Tailwind from a CDN at runtime requires allowing inline scripts.
- There is no real-world usage data. This is a personal project; I am not claiming users, adoption or production traffic.
Explore
The repository is public and includes the README with setup instructions, the environment variable
reference, the Make targets and the supporting documentation under docs/.
The repository README is written in Portuguese. There is no hosted demo instance, by design.