boilerplate

package module
v0.0.0-...-1252c86 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 21, 2021 License: Unlicense Imports: 15 Imported by: 1

README

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

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Default

func Default(value interface{}, fallback interface{}) interface{}

func If

func If(condition bool, yes interface{}, no interface{}) interface{}

func Must

func Must(err error)

Types

type Builder

type Builder struct{}

func CreateApp

func CreateApp() *Builder

func (*Builder) WithDatabase

func (b *Builder) WithDatabase(connectionFunc ...func(driverName string, dataSourceName string) error) *Builder

func (*Builder) WithGin

func (b0 *Builder) WithGin() *BuilderWithGin

func (*Builder) WithLogging

func (b *Builder) WithLogging() *Builder

type BuilderWithGin

type BuilderWithGin struct {
	Builder
	ListenAddress string
	Engine        *gin.Engine
}

func (*BuilderWithGin) Start

func (b *BuilderWithGin) Start()

func (*BuilderWithGin) WithStatic

func (b *BuilderWithGin) WithStatic(prefix string, fs http.FileSystem, allowIndex bool) *BuilderWithGin

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL