In a I was explaining the basics of setting up GO application for REST API. Now I'll go into details by first creating configurable server, adding http router (mux) and some DB interaction. Let's get (the party) started! previous post indoors The application is now running in docker, can respond to code changes and reload for instant feedback. For handling http requests I will add another dependency, http router (mux). You can read more about it . here This is a lightweight, high performance HTTP request router that is easy to use and has everything most api's will need. $ go get -u github.com/julienschmidt/httprouter Time to create the server. I'll place it in directory as it could potentially be reused: pkg/ server ( ) Server { srv *http.Server } { &Server{ srv: &http.Server{}, } } { s.srv.Addr = addr s } { s.srv.ErrorLog = l s } { s.srv.Handler = router s } { (s.srv.Addr) == { errors.New( ) } s.srv.Handler == { errors.New( ) } s.srv.ListenAndServe() } { s.srv.Close() } package import "errors" "log" "net/http" "github.com/julienschmidt/httprouter" type struct * func Get () Server return * func (s *Server) WithAddr (addr ) string Server return * func (s *Server) WithErrLogger (l *log.Logger) Server return * func (s *Server) WithRouter (router *httprouter.Router) Server return func (s *Server) Start () error if len 0 return "Server missing address" if nil return "Server missing handler" return func (s *Server) Close () error return As usual, function returns pointer to our server instance that exposes some public methods that are pretty self explanatory. It will become more obvious when I put this server to work in the main program. Get Server will need routes and handlers to communicate with outside world. I'll add that next: router ( ) { mux := httprouter.New() mux.GET( , getuser.Do(app)) mux } // cmd/api/router/router.go package import "github.com/boilerplate/cmd/api/handlers/getuser" "github.com/boilerplate/pkg/application" "github.com/julienschmidt/httprouter" * . func Get (app *application.Application) httprouter Router "/users" return getuser ( ) { { fmt.Fprintf(w, ) } } // cmd/api/handlers/getuser/getuser.go package import "fmt" "net/http" "github.com/boilerplate/pkg/application" "github.com/julienschmidt/httprouter" . func Do (app *application.Application) httprouter Handle return func (w http.ResponseWriter, r *http.Request, ps httprouter.Params) "hello" I define all my routes in and call handlers by explicitly passing application configuration so that each handler has access to things like database, configuration with env vars and more. router.go I keep my handlers separate from router and group them under sub folders . One reason is that handler will have corresponding test file. It will also have multiple middleware files that will also have tests and there can be a lot of handlers. If not grouped correctly, it can get out of hand very fast. cmd/api/handlers/{handlerName} Now there are few more building blocks: server, router, logger. Let's assemble it all in the main program: main ( ) { err := godotenv.Load(); err != { logger.Info.Println( ) } app, err := application.Get() err != { logger.Error.Fatal(err.Error()) } srv := server. Get(). WithAddr(app.Cfg.GetAPIPort()). WithRouter(router.Get(app)). WithErrLogger(logger.Error) { logger.Info.Printf( , app.Cfg.GetAPIPort()) err := srv.Start(); err != { logger.Error.Fatal(err.Error()) } }() exithandler.Init( { err := srv.Close(); err != { logger.Error.Println(err.Error()) } err := app.DB.Close(); err != { logger.Error.Println(err.Error()) } }) } // cmd/api/main.go package import "github.com/boilerplate/cmd/api/router" "github.com/boilerplate/pkg/application" "github.com/boilerplate/pkg/exithandler" "github.com/boilerplate/pkg/logger" "github.com/boilerplate/pkg/server" "github.com/joho/godotenv" func main () if nil "failed to load env vars" if nil go func () "starting server at %s" if nil func () if nil if nil New things to mention is that I assemble the server by chaining method calls and setting properties on the instance. One interesting thing is , this is just to instruct the server to use my custom logger for consistency. WithErrLogger(logger.Error) I start the server in separate go routine so that can still run and gracefully handle program shutdowns. contains 2 instances of standard library Logger. is printing out messages to and to . I could have used some fancy logger like or any other but I was planning to keep it simple. exithandler pkg/logger Info os.Stdout Error os.Stderr logrus Next let's take care of the databases. I use migration tool written in GO that can be used as CLI or as a library. You can read more about it and find installation instructions . After installing it, let's create few migration files. As seen from above, I'll be operating on resource so it's natural I'll have table: here /users users $ migrate create -ext sql -dir ./db/migrations create_user This will generate 2 migration files in , and for user table. All files are empty so let's add some sql. db/migrations up down Up: public.users ( PRIMARY , username ( ) ); -- db/migrations/${timestamp}_create_user.up.sql CREATE TABLE IF NOT EXISTS id SERIAL KEY VARCHAR 100 NOT NULL UNIQUE And down: public.users -- db/migrations/${timestamp}_create_user.down.sql DROP TABLE Pretty simple, but that's how it should be, right? Before running migrations, let's use library and create a program to simplify this process. This will also work nicely in CI/CD pipelines as it will let us skip the installation of cli as a separate step of the pipeline build. For that to happen I'll add yet another dependency: golang-migrate golang-migrate $ - github. /golang-migrate/migrate/v4 go get u com I'll name my program : dbmigrate main ( _ _ ) { godotenv.Load() cfg := config.Get() direction := cfg.GetMigration() direction != && direction != { log.Println( ) } m, err := migrate.New( , cfg.GetDBConnStr()) err != { log.Printf( , err) } direction == { err := m.Up(); err != { log.Printf( , err) } } direction == { err := m.Down(); err != { log.Printf( , err) } } } // cmd/dbmigrate/main.go package import "log" "github.com/boilerplate/pkg/config" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/postgres" "github.com/golang-migrate/migrate/v4/source/file" "github.com/joho/godotenv" func main () if "down" "up" "-migrate accepts [up, down] values only" return "file://db/migrations" if nil "%s" return if "up" if nil "failed migrate up: %s" return if "down" if nil "failed migrate down: %s" return Quick overview of what's happening here. First of all I load env vars. I then get pointer to instance of config that will give me easy access to all vars I need with some helper methods. You might have noticed that there's a new method. It will simply return or string to instruct my program if it should migrate database up or down. You can see latest changes . GetMigration up down here Now since I have this tool in place I can put it to work. Best place I found for it is By running it there I'm avoiding common "oh, I forgot to run migrations" issue. Updated version of : scripts/entripoint.dev.sh entrypoint.dev.sh -e go run cmd/dbmigrate/main.go go run cmd/dbmigrate/main.go -dbname=boilerplatetest GO111MODULE=off go get github.com/githubnemo/CompileDaemon CompileDaemon --build= -- =./main #!/bin/bash set "go build -o main cmd/api/main.go" command What's happening here? First run of will use all default values from .env file, so it will run the migrations up against db. In second run I pass so that it does the same but against db. Next I'll start my app with clean state: dbmigrate boilerplate -dbname=boilerplatetest boilerplatetest docker container rm -f $(docker container ps -a -q) docker volume prune -f docker-compose up --build # remove all containers # clear volumes # start app If all the above has worked, we should see table in both and databases. Let's check that: users boilerplate boilerplatetest docker -it $(docker ps --filter name=pg --format ) /bin/bash psql -U postgres -W \l \c boilerplate \dt \c boilerplatetest \dt # connect to pg docker container exec "{{.Names}}" # launch psql cli # ensure both DBs still present # connect to boilerplate database and list tables # do same for boilerplatetest # in both databases you should see users table This is what I see when the above commands are run: And sure thing it's all as expected. Now what if we add new migrations while application is running in docker. I'm pretty sure it's not very convenient to stop docker-compose and rerun the command again for changes to take place. Well, program is capable of handling this scenario. In new terminal tab: dbmigrate go run cmd/dbmigrate/main.go \ -migrate=down \ -dbname=boilerplatetest \ -dbhost=localhost go run cmd/dbmigrate/main.go \ -migrate=up \ -dbname=boilerplatetest \ -dbhost=localhost # migrate boilerplatetest db down # you can now repeat steps from above to connect to pg container # and ensure that users table is missing from boilerplatetest DB. # now bring it back up One thing to mention here is . This is because we connect to pg container from our host machine. Within docker-compose we can refer to same container by service name, which is , but we can't do same from host. -dbhost=localhost pg I hope you have learned something useful. In part 3 I'll go through simple CRUD operations for our users resource. It will include middleware usage, validations and more. You can also see the whole project and follow the progress . Stay safe! here