You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
Moritz Marquardt 1252c86d66 Add <DATABASE>_SERVER environment variable for host:port format 3 months ago
builders Add ApplyContext builder helper 5 months ago
cmd/example Copy all boilerplate code to a nice seperate repository 5 months ago
env Make environment helper use log.Fatal() instead of panic() 5 months ago
parsable Add environment helper module & improve helper documentation 5 months ago
request Fix documentation issues 5 months ago
.gitignore Swap exclude/include lines in .gitignore so it can be symlinked from .dockerignore (fixes #1) 5 months ago
Dockerfile Copy all boilerplate code to a nice seperate repository 5 months ago
LICENSE Copy all boilerplate code to a nice seperate repository 5 months ago
README.md Recommend gin-swagger & link the error handling example (fixes #3) 5 months ago
builder.go Only allow .WithStatic() if .WithGin() has been called 5 months ago
database.go Add <DATABASE>_SERVER environment variable for host:port format 3 months ago
docker-compose.yml Add missing environment variables and port forwards in docker-compose.yml (fixes #2) 5 months ago
gin.go Make environment helper use log.Fatal() instead of panic() 5 months ago
go.mod Remove sqlite from required dependencies 3 months ago
go.sum Add gin-request module for easier parameter handling 5 months ago
helpers.go Copy all boilerplate code to a nice seperate repository 5 months ago
log.go Make environment helper use log.Fatal() instead of panic() 5 months ago
static.go Only listen on the specified prefix on static routes 5 months ago

README.md

Webapp Boilerplate for Go

Creating a web server with Go is easy, but making it good requires quite some work - especially logging, error handling, database connection and static resources are in most cases required, but need a lot of work to implement every time.

This module replaces your boilerplate code and decreases your time to get started with the actual work. It's slightly opinionated (i.e. you'll have to use Gin and Zerolog, otherwise you're free to use whatever you want - Pkger is the recommended way to package static assets, Ent is the recommended ORM, and Swag with gin-swagger the recommended way to document your API).

To make your life even easier, there are also helpers for request arguments, as well as for environment variables.

To get started with a new project, initialize a new Go module with go mod init ... and create a cmd/.../main.go file according to the example:

package main

import (
	"codeberg.org/momar/webapp-boilerplate"
	"codeberg.org/momar/webapp-boilerplate/environment"
	"database/sql"
	"github.com/gin-gonic/gin"
	"github.com/markbates/pkger"
	"github.com/rs/zerolog/log"
	"io/ioutil"
	".../ent"
)

//go:generate pkger

var db *ent.Client

var prefix = env.Read("PREFIX").Default("example-").AsString().Get()

func main() {
	// Open static files
	staticFiles, err := pkger.Open("/")
	boilerplate.Must(err)

	app := boilerplate.CreateApp().
		WithLogging().
		WithDatabase(func(driverName string, dataSourceName string) (err error) {
			// Connect to the database
			log.Info().Str("driver", driverName).Str("dataSource", dataSourceName).Msg("Connecting to database.")
			db, err = ent.Open(driverName, dataSourceName)
			return
		}, func(string, string) (err error) {
			// Apply schema
			return db.Schema.Create(context.Background());
		}).
		WithGin().
		WithStatic("/", staticFiles, true)

	app.Engine.GET("/api/:key", func(c *gin.Context) {
		// Retrieve value from database
		value := db.Data.
			GetX(c, prefix + c.Param(key)).
			Value
		c.String(200, value)
	})

	app.Engine.PUT("/api/:key", func(c *gin.Context) {
		// Read body
		value, err := ioutil.ReadAll(c.Request.Body)
		boilerplate.Must(err)

		// Try to update existing field in database
		n := db.Data.
			UpdateID(prefix + c.Param("key")).
			SetValue(string(value)).
			SaveX(c)
		if n == 0 {
			// If no field exist, create a new one
			db.Data.Create().
				SetID(prefix + c.Param("key")).
				SetValue(string(value)).
				SaveX(c)
		}

		c.String(200, "ok")
	})

	app.Start()
}

Additionally, you should copy & adjust the .gitignore, Dockerfile and docker-compose.yml from this repository to get a clean & fully packaged web application.

You can also create a symlink from .dockerignore to .gitignore (ln -s .gitignore .dockerignore) to use the same ignore configuration for both Git and Docker.

When creating an API, you should create your own error handling function for all routes that are using Ent (see error-handling.go in wuks/reservator for an example) - in this project, you can also find an example on how to do API documentation with Swag & gin-swagger.

Available methods

WithLogging()

Sets up Zerolog for nice console output & improved Gin logging.

You can use the following environment variables to modify the runtime behaviour:

  • LOG_LEVEL: minimum level for logging, available values are trace, debug, info, warn and error, the default is info. For trace and debug, Gin is set to debug mode
  • LOG_FORMAT: format for logging, either json or console, the latter is the default value.

WithDatabase(...func(driverName string, dataSourceName string) error)

Parses environment variables to determine the database connection & calls the supplied functions with the driverName & dataSourceName as expected by database/sql. It currently supports MySQL, PostgreSQL and SQLite, using the following environment variables:

  • SQLITE_DATABASE: filename of the SQLite3 database. By default, an in-memory SQLite3 database will be used!
  • MYSQL_DATABASE: database name for MySQL. Required when using MySQL!
  • MYSQL_HOST: hostname of the MySQL server, defaults to 127.0.0.1.
  • MYSQL_PORT: port of the MySQL server, defaults to 3306.
  • MYSQL_USER: username for the MySQL connection, defaults to root.
  • MYSQL_PASSWORD: password for the MySQL connection, defaults to either the value of MYSQL_ROOT_PASSWORD if the user is root, or an empty string.
  • POSTGRES_DB: database name for MySQL. Required when using PostgreSQL!
  • POSTGRES_HOST: hostname of the PostgreSQL server, defaults to 127.0.0.1.
  • POSTGRES_PORT: port of the PostgreSQL server, defaults to 5432.
  • POSTGRES_SSLMODE: encryption mode for the connection, defaults to disable. See pq documentation for more information.
  • POSTGRES_USER: username for the PostgreSQL connection, defaults to postgres.
  • POSTGRES_PASSWORD: password for the PostgreSQL connection, defaults to an empty string.

WithGin()

Sets up a Gin engine with a logger (if LOG_LEVEL is at least debug) and recovery & handling of panic() (send 500 & log only to STDOUT) and c.AbortWithError() (send the error to the client).

  • HOST: the hostname to listen on, defaults to [::] (everywhere).
  • PORT: the port to listen on, defaults to 80 if os.Geteuid() is 0 (root), otherwise 8080.

WithStatic(prefix, fs, allowIndex)

Adds a static file middleware to the Gin engine at the specified prefix. As opposed to the equivalent Gin functions, this will not block subordinate routes from being assigned.

Start()

Start the Gin webserver.

Future

  • Full example application
  • Recommendation for Authentication (JWT, OAuth2, LDAP, SAML) & Permissions
  • Recommendation for i18n
  • Recommendation for admin/config dashboards (like Django)
  • Documentation for adding a Vue 3 frontend
  • Kubernetes support