diff --git a/Dockerfile b/Dockerfile index 378b6cd..d190d89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,26 @@ -FROM golang:alpine AS builder +FROM golang AS builder + +WORKDIR /src +# Download dependencies +COPY go.mod go.sum / +RUN go mod download # Add source code -ADD ./ /go/src/github.com/dhax/go-base/ - -RUN cd /go/src/github.com/dhax/go-base && \ - go build && \ - mv ./go-base /usr/bin/go-base +COPY . . +RUN CGO_ENABLED=0 go build -o main . # Multi-Stage production build -FROM alpine - -RUN apk add --update ca-certificates +FROM alpine AS production +RUN apk --no-cache add ca-certificates +WORKDIR /app # Retrieve the binary from the previous stage -COPY --from=builder /usr/bin/go-base /usr/local/bin/go-base - +COPY --from=builder /src/main . +# Copy static template files +COPY templates templates +# Copy frontend +COPY public public +# Expose port +EXPOSE 3000 # Set the binary as the entrypoint of the container -CMD ["go-base", "serve"] \ No newline at end of file +CMD ["./main", "serve"] \ No newline at end of file diff --git a/README.md b/README.md index bf7ea4e..293fb02 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Go Restful API Boilerplate -[![GoDoc Badge]][GoDoc] [![GoReportCard Badge]][GoReportCard] +[![GoDoc Badge]][godoc] [![GoReportCard Badge]][goreportcard] Easily extendible RESTful API boilerplate aiming to follow idiomatic go and best practice. @@ -9,6 +9,7 @@ The goal of this boiler is to have a solid and structured foundation to build up Any feedback and pull requests are welcome and highly appreciated. Feel free to open issues just for comments and discussions. ## Features + The following feature set is a minimal selection of typical Web API requirements: - Configuration using [viper](https://github.com/spf13/viper) @@ -16,82 +17,91 @@ The following feature set is a minimal selection of typical Web API requirements - PostgreSQL support including migrations using [go-pg](https://github.com/go-pg/pg) - Structured logging with [Logrus](https://github.com/sirupsen/logrus) - Routing with [chi router](https://github.com/go-chi/chi) and middleware -- JWT Authentication using [jwt-go](https://github.com/dgrijalva/jwt-go) with example passwordless email authentication +- JWT Authentication using [golang-jwt](https://github.com/golang-jwt/jwt/v4) with example passwordless email authentication - Request data validation using [ozzo-validation](https://github.com/go-ozzo/ozzo-validation) - HTML emails with [gomail](https://github.com/go-gomail/gomail) ## Start Application + - Clone this repository - Create a postgres database and set environment variables for your database accordingly if not using same as default -- Run the application to see available commands: ```go run main.go``` -- First initialize the database running all migrations found in ./database/migrate at once with command *migrate*: ```go run main.go migrate``` -- Run the application with command *serve*: ```go run main.go serve``` +- Run the application to see available commands: `go run main.go` +- First initialize the database running all migrations found in ./database/migrate at once with command _migrate_: `go run main.go migrate` +- Run the application with command _serve_: `go run main.go serve` + +Or just use the provided docker-compose file. After first start attach to the server container and run `./main migrate` to populate the database. ## API Routes ### Authentication + For passwordless login following routes are available: -Path | Method | Required JSON | Header | Description ----|---|---|---|--- -/auth/login | POST | email | | the email you want to login with (see below) -/auth/token | POST | token | | the token you received via email (or printed to stdout if smtp not set) -/auth/refresh | POST | | Authorization: "Bearer refresh_token" | refresh JWTs -/auth/logout | POST | | Authorizaiton: "Bearer refresh_token" | logout from this device +| Path | Method | Required JSON | Header | Description | +| ------------- | ------ | ------------- | ------------------------------------- | ----------------------------------------------------------------------- | +| /auth/login | POST | email | | the email you want to login with (see below) | +| /auth/token | POST | token | | the token you received via email (or printed to stdout if smtp not set) | +| /auth/refresh | POST | | Authorization: "Bearer refresh_token" | refresh JWTs | +| /auth/logout | POST | | Authorizaiton: "Bearer refresh_token" | logout from this device | ### Example API -Besides /auth/* the API provides two main routes /api/* and /admin/*, as an example to separate application and administration context. The latter requires to be logged in as administrator by providing the respective JWT in Authorization Header. + +Besides /auth/_ the API provides two main routes /api/_ and /admin/\*, as an example to separate application and administration context. The latter requires to be logged in as administrator by providing the respective JWT in Authorization Header. Check [routes.md](routes.md) for a generated overview of the provided API routes. - ### Client API Access and CORS -The server is configured to serve a Progressive Web App (PWA) client from *./public* folder (this repo only serves an example index.html, see below for a demo PWA client to put here). In this case enabling CORS is not required, because the client is served from the same host as the api. -If you want to access the api from a client that is serverd from a different host, including e.g. a development live reloading server with below demo client, you must enable CORS on the server first by setting environment variable *ENABLE_CORS=true* on the server to allow api connections from clients serverd by other hosts. +The server is configured to serve a Progressive Web App (PWA) client from _./public_ folder (this repo only serves an example index.html, see below for a demo PWA client to put here). In this case enabling CORS is not required, because the client is served from the same host as the api. + +If you want to access the api from a client that is serverd from a different host, including e.g. a development live reloading server with below demo client, you must enable CORS on the server first by setting environment variable _ENABLE_CORS=true_ on the server to allow api connections from clients serverd by other hosts. #### Demo client application -For demonstration of the login and account management features this API serves a demo [Vue.js](https://vuejs.org) PWA. The client's source code can be found [here](https://github.com/dhax/go-base-vue). Build and put it into the api's *./public* folder, or use the live development server (requires CORS enabled). -Outgoing emails containing the login token will be print to stdout if no valid email smtp settings are provided by environment variables (see table below). If *EMAIL_SMTP_HOST* is set but the host can not be reached the application will exit immediately at start. +For demonstration of the login and account management features this API serves a demo [Vue.js](https://vuejs.org) PWA. The client's source code can be found [here](https://github.com/dhax/go-base-vue). Build and put it into the api's _./public_ folder, or use the live development server (requires CORS enabled). + +Outgoing emails containing the login token will be print to stdout if no valid email smtp settings are provided by environment variables (see table below). If _EMAIL_SMTP_HOST_ is set but the host can not be reached the application will exit immediately at start. Use one of the following bootstrapped users for login: + - admin@boot.io (has access to admin panel) - user@boot.io A deployed version can also be found on [Heroku](https://govue.herokuapp.com) ### Environment Variables + By default viper will look at $HOME/.go-base.yaml for a config file. Setting your config as Environment Variables is recommended as by 12-Factor App. -Name | Type | Default | Description ----|---|---|--- -PORT | string | localhost:3000 | http address (accepts also port number only for heroku compability) -LOG_LEVEL | string | debug | log level -LOG_TEXTLOGGING | bool | false | defaults to json logging -DB_NETWORK | string | tcp | database 'tcp' or 'unix' connection -DB_ADDR | string | localhost:5432 | database tcp address or unix socket -DB_USER | string | postgres | database user name -DB_PASSWORD | string | postgres | database user password -DB_DATABASE | string | gobase | database shema name -AUTH_LOGIN_URL | string | http://localhost:3000/login | client login url as sent in login token email -AUTH_LOGIN_TOKEN_LENGTH | int | 8 | length of login token -AUTH_LOGIN_TOKEN_EXPIRY | time.Duration | 11m | login token expiry -AUTH_JWT_SECRET | string | random | jwt sign and verify key - value "random" creates random 32 char secret at startup (and automatically invalidates existing tokens on app restarts, so during dev you might want to set a fixed value here) -AUTH_JWT_EXPIRY | time.Duration | 15m | jwt access token expiry -AUTH_JWT_REFRESH_EXPIRY | time.Duration | 1h | jwt refresh token expiry -EMAIL_SMTP_HOST | string || email smtp host (if set and connection can't be established then app exits) -EMAIL_SMTP_PORT | int || email smtp port -EMAIL_SMTP_USER | string || email smtp username -EMAIL_SMTP_PASSWORD | string || email smtp password -EMAIL_FROM_ADDRESS | string || from address used in sending emails -EMAIL_FROM_NAME | string || from name used in sending emails -ENABLE_CORS | bool | false | enable CORS requests +| Name | Type | Default | Description | +| ----------------------- | ------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PORT | string | localhost:3000 | http address (accepts also port number only for heroku compability) | +| LOG_LEVEL | string | debug | log level | +| LOG_TEXTLOGGING | bool | false | defaults to json logging | +| DB_NETWORK | string | tcp | database 'tcp' or 'unix' connection | +| DB_ADDR | string | localhost:5432 | database tcp address or unix socket | +| DB_USER | string | postgres | database user name | +| DB_PASSWORD | string | postgres | database user password | +| DB_DATABASE | string | postgres | database shema name | +| AUTH_LOGIN_URL | string | http://localhost:3000/login | client login url as sent in login token email | +| AUTH_LOGIN_TOKEN_LENGTH | int | 8 | length of login token | +| AUTH_LOGIN_TOKEN_EXPIRY | time.Duration | 11m | login token expiry | +| AUTH_JWT_SECRET | string | random | jwt sign and verify key - value "random" creates random 32 char secret at startup (and automatically invalidates existing tokens on app restarts, so during dev you might want to set a fixed value here) | +| AUTH_JWT_EXPIRY | time.Duration | 15m | jwt access token expiry | +| AUTH_JWT_REFRESH_EXPIRY | time.Duration | 1h | jwt refresh token expiry | +| EMAIL_SMTP_HOST | string | | email smtp host (if set and connection can't be established then app exits) | +| EMAIL_SMTP_PORT | int | | email smtp port | +| EMAIL_SMTP_USER | string | | email smtp username | +| EMAIL_SMTP_PASSWORD | string | | email smtp password | +| EMAIL_FROM_ADDRESS | string | | from address used in sending emails | +| EMAIL_FROM_NAME | string | | from name used in sending emails | +| ENABLE_CORS | bool | false | enable CORS requests | ### Testing + Package auth/pwdless contains example api tests using a mocked database. -[GoDoc]: https://godoc.org/github.com/dhax/go-base -[GoDoc Badge]: https://godoc.org/github.com/dhax/go-base?status.svg -[GoReportCard]: https://goreportcard.com/report/github.com/dhax/go-base -[GoReportCard Badge]: https://goreportcard.com/badge/github.com/dhax/go-base +[godoc]: https://godoc.org/github.com/dhax/go-base +[godoc badge]: https://godoc.org/github.com/dhax/go-base?status.svg +[goreportcard]: https://goreportcard.com/report/github.com/dhax/go-base +[goreportcard badge]: https://goreportcard.com/badge/github.com/dhax/go-base diff --git a/cmd/serve.go b/cmd/serve.go index f02c4a0..2288ef6 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -26,7 +26,7 @@ func init() { RootCmd.AddCommand(serveCmd) // Here you will define your flags and configuration settings. - viper.SetDefault("port", "localhost:3000") + viper.SetDefault("port", "3000") viper.SetDefault("log_level", "debug") viper.SetDefault("auth_login_url", "http://localhost:3000/login") diff --git a/database/postgres.go b/database/postgres.go index 62b19ae..12aa4a6 100644 --- a/database/postgres.go +++ b/database/postgres.go @@ -15,7 +15,7 @@ func DBConn() (*pg.DB, error) { viper.SetDefault("db_addr", "localhost:5432") viper.SetDefault("db_user", "postgres") viper.SetDefault("db_password", "postgres") - viper.SetDefault("db_database", "gobase") + viper.SetDefault("db_database", "postgres") db := pg.Connect(&pg.Options{ Network: viper.GetString("db_network"), diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..461fdd7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,42 @@ +version: "3.8" + +volumes: + postgres: + +services: + server: + build: + context: . + depends_on: + - postgres + ports: + - 3000:3000 + environment: + LOG_LEVEL: debug + LOG_TEXTLOGGING: "true" + #PORT: 3000 + DB_ADDR: postgres:5432 + #DB_USER: postgres + #DB_PASSWORD: postgres + #DB_DATABASE: postgres + #AUTH_JWT_EXPIRY: 1h + #AUTH_JWT_REFRESH_EXPIRY: 72h + #AUTH_JWT_SECRET: my secret + #SENDGRID_API_KEY: your-sendgrid-api-key + #EMAIL_FROM_ADDRESS: go-base + #EMAIL_FROM_NAME: Go Base + #EMAIL_SMTP_HOST: + #EMAIL_SMTP_PORT: 465 + #EMAIL_SMTP_USER: + #EMAIL_SMTP_PASSWORD: + ENABLE_CORS: "true" + + postgres: + image: postgres:13 + restart: unless-stopped + ports: + - 5432:5432 + volumes: + - postgres:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: postgres \ No newline at end of file