In this new series we will be creating an API written in go, using a framework like Chi, connecting to a PostgreSQL, and have it deployed to a site like Railway.
The project will be hosted in GitHub
Requirements
In order to follow this tutorial, we will need to have the following installed:
- The Go programing language, you can do this by following the instructions in the Go website
- The PostgreSQL database, to install just follow the instructions in the PostgreSQL website
- Git and GitHub for version control and remote repository
- Railway Account so you will have a place to deploy the application
What are we going to be building
Through this tutorial, we will be creating an API which will be tracking a book store and store comments for each of the books.
We also will have a form of authentication, where we will sign in users, and with that we will implement a basic authorization module which will give some permissions to users.
We will be adding both unit and integration tests, so we can ensure that we are accomplishing each of the requirements of our MVP
Finally, each endpoint we will be creating should return the appropriate HTTP Code and the response will be using the JSON format
Setting up the project
- Create the directory where we will be coding the application
mkdir bookstore
cd bookstore
- Initialize
Gitfor the project. It is my personal preference to add an emptycommitas my initial commit
git init
git commit --allow-empty -m "Intial Commit"
- Initialize the
Goproject
go mod init github.com/alcb1310/bookstore
On this step we created the go.mod file and added the posibility to add libraries to our project
- API entry point. Now we will start using best practices on where to place each file. Even though your main file could be placed in the project's root, it is a good practice to save it inside the
/cmd/apidirectory
mkidr -p cmd/api
Inside the /cmd/api folder we will create the main.go file as our application entry point
package main
import fmt
func main() {
fmt.Println("Hello bookstore")
}
To run the project, you can execute
go run ./cmd/api/main.go
and the following will be displayed
Enabling Hot Reload
At the moment we have an entry point for our application, but we will need to stop and rerun manually each time we make changes in the application. There is a tool we can use and that one is Air, so let's setup it
- Install Air in your system
go install github.com/air-verse/air@latest
To make sure you've installed air in your system run
air -v
- Create a
.air.tomlconfiguration file, with this step we will setup some defaults for our project that will direct which file is your entry point
root = "."
tmp_dir = "tmp"
[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./build/main ./cmd/api/main.go"
# Entrypoint binary relative to root. First item is the executable, more items are default arguments.
entrypoint = ["./build/main"]
delay = 1000
# Ignore these filename extensions or directories.
exclude_dir = ["build"]
exclude_file = []
# Exclude specific regular expressions.
exclude_regex = ["_test\\.go"]
log = "build-errors.log"
[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"
[misc]
clean_on_exit = true
[screen]
clear_on_rebuild = true
keep_scroll = true
Ignoring files for source contrl
As the final step in this article, there are files that we do not want to track its changes such as the binaries we will be generating, like the ones we will save in the build directory, we will need to create a .gitignore file for it
build
tmp
Adding our first route
Next step is to start using Chi, first we will need to add it to our project
go get -u github.com/go-chi/chi/v5
It is a best practice to have our entry point as small as possible and have each process split in different modules, so following that, we will create a router module
mkidr -p internal/router
The internal folder in Go makes that all the modules we create inside it, will not be visible from other projects, that is why we want to have our business logic inside it
internal/router/router.go
package router
import (
"fmt"
"log/slog"
"net/http"
"github.com/go-chi/chi/v5"
)
type service struct {
port uint16
}
func New(port uint16) *service {
return &service{
port: port,
}
}
func (s *service) Router() error {
r := chi.NewRouter()
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
})
port := fmt.Sprintf(":%d", s.port)
slog.Info("Starting server", "port", port)
return http.ListenAndServe(port, r)
}
cmd/api/main.go
package main
import "github.com/alcb1310/bookstore/internal/router"
func main() {
var port uint16 = 8080
s := router.New(port)
if err := s.Router(); err != nil {
panic(err)
}
}
Reading from environment
Now we have our application working, but we have hardcoded the PORT in which the application will be listening to, to solve that we can use environment variables in a .env file at the root of our project.
To do so, we will first need to install a package
go get github.com/joho/godotenv
With that we can change our main.go file
main.go
...
import (
"log/slog"
"os"
"strconv"
...
_ "github.com/joho/godotenv/autoload"
)
func main() {
port64, err := strconv.ParseUint(os.Getenv("PORT"), 10, 16)
if err != nil {
slog.Error("Error parsing port", "error", err)
panic(err)
}
port := uint16(port64)
...
}
Deploying the application
Now we have the application it its most basic form, so it is a good idea to deploy it.
- First we need to push all of our code to GitHub
- In railway home page, we click in the deploy button
- We select GitHub Repository and select the repo you are using
- Select the repo and go to the settings tab
- In the Networking section, select Generate Domain, and select the default PORT 8080
After its done, click on the link provided and you should be able to see the message we added to our route, meaning our project is deployed and running, and every change we make in the main branch in GitHub will be deployed in railway
Deploying PR
Railway has a feature where you can automatically deploy PR's, it is always a good idea to enable them so you can test your changes before merging them, you can find that option in the project's settings page under the Environments tab






Top comments (0)