This is my (embiem's) favorite Go web stack at the time of making.
I use this to start new web projects quickly and will likely change this template over time.
The simple password auth in this template is just to get going and should be replaced with a more secure approach or additional best practices, before going to production. Always reference the OWASP Top 10 list to ensure you're building a secure app.
- install Go (version 1.23.1)
- install migrate CLI
- example... check releases page:
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.1/migrate.linux-amd64.tar.gz | tar xvz
- example... check releases page:
- install sqlc
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
- install air
go install github.com/air-verse/air@latest
- install templ
go install github.com/a-h/templ/cmd/templ@latest
- install tailwindcss-cli v3.4.10
- example... check releases page:
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 && chmod +x tailwindcss-linux-x64 && mv tailwindcss-linux-x64 tailwindcss
- example... check releases page:
cp .env.example .env
& fill-in any missing env vars- spin-up local dev services like db:
docker compose up -d
- run local dev setup via
air
- run tests via
go test ./...
Using golang-migrate for migrations (Tutorial) and sqlc for queries, mutations & codegen (Tutorial).
sqlc doc about handling SQL migrations.
For local dev, setup env var like so: export POSTGRESQL_URL='postgres://postgres:password@localhost:5432/postgres?sslmode=disable'
.
Optionally, test migrations up & down on a separate local db instance e.g. by spinning up a stack with different name: docker compose -p dbmigrations-testing up -d
.
- Create Migration files:
migrate create -ext sql -dir db/migrations -seq your_migration_description
- Write the migrations in the created up & down files using SQL
- Run up migrations:
migrate -database ${POSTGRESQL_URL} -path db/migrations up
- Check db & run down migrations to test they work as well:
migrate -database ${POSTGRESQL_URL} -path db/migrations down
& check db as well - run up migrations again
When dirty, force db to a version reflecting it's real state: migrate -database ${POSTGRESQL_URL} -path db/migrations force VERSION
Important: Write migration SQL in transactions. In Postgres, when we want our queries to be done in a transaction, we need to wrap it with BEGIN
and COMMIT
commands. Example:
-- up migration
BEGIN;
CREATE TYPE enum_mood AS ENUM (
'happy',
'sad',
'neutral'
);
ALTER TABLE users ADD COLUMN mood enum_mood;
COMMIT;
-- down migration
BEGIN;
ALTER TABLE users DROP COLUMN mood;
DROP TYPE enum_mood;
COMMIT;
Write the SQL queries & mutations in db/query.sql
and then run sqlc generate
.
Using Templ: https://templ.guide/quick-start/creating-a-simple-templ-component
templ generate
to generate go files after adding or editing .templ files
Here is a list of tips to optimize the loading times.
- Only include the JS that's needed. If a page doesn't need any JS, don't include it.
- Include any external JS scripts with the
defer
attribute. - Preload pages e.g. on hover via htmx preload extension
- Use a CDN to serve assets & ideally pre-rendered HTML pages as well
- Cache assets as well as pages as much as possible
- Add links to needed assets like images or fonts with a
rel="preload"
attribute to the head. - Add links to other domains like your CDN with a
rel="dns-prefetch"
attribute to the head. - Defer non-critical CSS (web.dev guide): Include all necessary CSS for a page in the HTML on first load. Defer the load of the general CSS file that covers other pages/non-critical css.
- Make use of image sprites, e.g. if loading many thumbnails of same size, to reduce amount of requests & re-renders.