A reusable set of micro-services for a multi-tenant SaaS backend.
demo | introductory blog post | API playground
- Authentication & Authorization: service | design | API playground
This project is integrated with a compatible frontend built with Flutter (for web). It can be used with any frontend technology since the exposed services offer complete REST APIs.
Choose your environment dev
(default), test
or prod
:
export SAAS_KIT_ENV="test"
Create an environment file .env.dev
or .env.test
for the environments used, where you might want to override some env-specific variables.
Create a local postgres instance or use the provided docker-compose.yml
file to set it up:
docker network create saas-kit-net
docker volume create --name=saas-kit-pg
docker-compose --env-file .env.dev up
Initialize separate database schemas, one for each service (in production you have service-local databases with only one schema):
go run ./cmd/dev init
Migrate services:
go run ./cmd/auth migrate
go run ./cmd/event migrate
Run single service
go run ./cmd/auth
Run all sevices by separate go routines (migrations are not applied!):
go run ./cmd/dev
Load fixtures from yaml file with a default instance:
go run ./cmd/auth fixture ./pkg/auth/fixtures/instances.yml
Create super user:
go run ./cmd/auth adduser -name=Simon -email=simon@smartnuance.com -password=f00bartest -instance=smartnuance.com
Use exposed endpoint to signup user (with no permissions to start with):
http PUT :8801/signup instance="smartnuance.com" name=Bob email=bob@smartnuance.com password=alice
Test login and save refresh/access tokens:
RES=$(http POST :8801/login email=simon@smartnuance.com password=f00bartest instance=smartnuance.com -v -b)
RT=$(echo $RES | jq -r '.refreshToken')
AT=$(echo $RES | jq -r '.accessToken')
Refresh token:
http -v POST :8801/refresh refreshToken=$RT
Revoke token:
http -v DELETE :8801/revoke/ Authorization:"Bearer $AT"
Revoke all tokens:
http -v DELETE :8801/revoke/all Authorization:"Bearer $AT"
For a while, I can still use the access token, for example to rerun the idem-potent revoke:
http -v DELETE :8801/revoke/ Authorization:"Bearer $AT"
But if we try to use the revoked refresh token in a refresh call, this will fail:
http -v POST :8801/refresh refreshToken=$RT
Since no implicit switch from the super admin is allowed, we provide the role header to temporarily switch to the event organizer role:
http -v PUT :8802/workshop Authorization:"Bearer $AT" role:"event organizer" instance:"c5263570ono4ui8qfhgg" title=Bachata locationName=Ponto
API framework:
Database interaction:
- sqlboiler for generated db interaction
- golang-migrate for clean up/down migrations
- Globally Unique ID Generator that uses Mongo Object ID algorithm
Token handling:
- jwt-go (v4!)
Logging:
Asset handling:
Environment & Building:
Testing:
All tools necessary for development like installing code generators are listed in tools.go
.
(a subset of those tools are installed in CI build step of Earthfile
)
To run all generators recursively:
go generate ./...
This should also trigger the protobuf generation with protoc
defined in go:generate
in tools.go
.
To interact with database, we use a schema first approach with sqlboiler. It generates type-safe code to interact with the DB.
To start from an empty database (and test down migrations):
go run ./cmd/auth migrate -down
Or if you messed the database up and want to start from scratch, you can reinit the database (data will be lost!):
go run ./cmd/dev deinit
go run ./cmd/dev init
Migrate services:
go run ./cmd/auth migrate
go run ./cmd/event migrate
When database is on newest version, we have to generated git-versioned DB models by
go generate ./pkg/auth/db.go
To include build information we use the govvv
utility:
govvv build -pkg github.com/smartnuance/saas-kit/pkg/auth -o ./bin/ ./cmd/auth
To create reproducable builds, you can use EARTHLY:
earthly --build-arg service=auth +build
With either way the resulting runnable is executed by:
./bin/auth
Again with EARTHLY, we can switch the target to deploy
:
earthly --build-arg service=dev +publish
This will not yet push the created single-binary docker image.
You can try to run the docker image locally, using the host network (so the ports used by the services needs to be free):
docker run --rm --network host -v $PWD/test/data:/app/test/data ghcr.io/smartnuance/saas-kit:latest
We are using the configurable staticcheck linter.
staticcheck ./...
Check that your VScode workspace settings contain
"go.lintTool": "staticcheck"
If your IDE (like VScode) shows broken test output because it does not support colored output, add this to your workspace settings:
"go.testEnvVars": {
"TESTDEEP_COLOR": "off"
},