Intro
In this post we will go through the process of building a simple Rest API go application.
Initially we will create a callback app in Go which will have one Rest API endpoint, then in the next part we will extend the application with CRUD DB Operations. That is, we are going to give our application a state and implement some form of authentication.
Create a new Project
First let us create a working directory named callback which from now on will call rootDir
mkdir -vp callback
cd callback
We should initiate go mod
in order to handle any
dependencies in our code automatically. To initiate go mode
run the following code by changing
ulfox
with your username.
go mod init github.com/ulfox/callback
The rootDir should now contain a file named go.mod
ls .
go.mod
Router
Before we create our main let us start by creating the router which will be the place that defines the logic for our Endpoint Handlers
We start by creating a new directory called router
mkdir -p router
Next create a file called router.go in router directory with the code below
package router
import (
"errors"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
// Service for creating a new router with logging
type Service struct {
Router *mux.Router
Name string
logger logrus.FieldLogger
}
// UpdateRoutes method updates main route with our Handle Functions
func (s *Service) UpdateRoutes() {
s.Router.HandleFunc("/api/v1/ping", s.Callback).Methods("GET")
}
// NewRouter factory for creating a Service router
func NewRouter(name string, logger logrus.FieldLogger) (*Service, error) {
if name == "" {
return nil, errors.New("Name not allowed to be empty")
}
return &Service{
Router: mux.NewRouter().StrictSlash(true),
Name: name,
logger: logger,
}, nil
}
In the code above we defined:
- Lines (2 - 8): The import block with the required modules.
- Lines (11 - 15): A Service for our router
- Lines (18 - 20): UpdateRoute Method that updates the main service route with the endpoint handle functions
- Lines (23 - 33): NewRouter factory for creating a new Service
The NewRouter factory will be used in our main.go to create a new router later on.
We now need to define an endpoint and a handler function for it. Our first endpoint will be /api/v1/ping and will be handled by the Calback function. Open a new file in router directory called endpoints.go and add the following code
package router
import (
"encoding/json"
"fmt"
"net/http"
"github.com/sirupsen/logrus"
)
// ResponseInfo for sending data back to the client
type ResponseInfo struct {
Client string `json:"client,omitempty"`
SecretKey string `json:"secret_key,omitempty"`
Message string `json:"message,omitempty"`
Count int `json:"count,omitempty"`
Error string `json:"error,omitempty"`
RemoteAddr string `json:"-"`
URL string `json:"-"`
Handler string `json:"-"`
}
// requestHandler for handling request
func (s *Service) requestHandler(responseInfo ResponseInfo, httpErr int, w http.ResponseWriter) {
s.logger.WithFields(logrus.Fields{
"Server": s.Name,
"Client": responseInfo.RemoteAddr,
"URL": responseInfo.URL,
"Address": responseInfo.RemoteAddr,
"Handler": responseInfo.Handler,
"Error": responseInfo.Error,
}).Info(responseInfo.Message)
w.WriteHeader(httpErr)
jsonResponse, err := json.Marshal(responseInfo)
if err != nil {
w.WriteHeader(400)
fmt.Fprintf(
w,
err.Error(),
)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResponse)
}
// Callback method for implementing a simple ping pong response
func (s *Service) Callback(w http.ResponseWriter, r *http.Request) {
responseInfo := ResponseInfo{
RemoteAddr: r.RemoteAddr,
URL: r.URL.String(),
Handler: "Callback",
}
responseInfo.Message = "pong"
s.requestHandler(responseInfo, 200, w)
}
- Lines (2 - 9): We defined the import block with the required package dependencies
- Lines (12 - 21) ResponseInfo struct for creating response requests
- Lines (24 - 46 ) requestHandler method for managing responses. This method calls logger to log any request made and sends a json response to the client
- Lines (48 - 58) Callback method that handles ping-pong requests on /api/v1/ping endpoint.
Note: Most of the entries in ResponseInfo will be used in part 2 of this blog. Link for part 2 can be found at the end of this post.
For the scope of part-1 (this post) we now have all the code we need in the router package. In the next part we will further extend the route package
Create Go App
We are now ready to create our http app. Create a file called main.go
and
append the code below.
package main
import (
"net/http"
"os"
"github.com/sirupsen/logrus"
"github.com/ulfox/callback/router"
)
// Global variables
var (
logger = logrus.New()
SRVER string = "GoWebapp"
)
func die(stage, msg string) {
logger.WithFields(logrus.Fields{
"Component": "Main",
"Stage": stage,
}).Error(msg)
os.Exit(1)
}
func main() {
logger.WithField("Server", SRVER).Infof("Initiated")
service, err := router.NewRouter(SRVER, logger)
if err != nil {
die("NewRouter", err.Error())
}
err = http.ListenAndServe(":8080", service.Router)
if err != nil {
die("ListenAndServe", err.Error())
}
}
Note: Please note how we import our router. If you changed ulfox with your name, do not forget to also change it in the code above.
- Lines (2 - 7): In the code above we have our import block that has the required modules that we need
- Lines (10 - 13): We define the logger and SRVER global variablees.
logger: We define a global logger that can be used from all functions in main.go
SRVER: This is the name of our app. We add it in the global section so it can be referenced
- Lines (15 - 21) defined a simple die function. It takes two arguments, a name for what caused the error and the error
- Lines (23 - 35) define the main function which on start logs an info message, defines a router and listens on port 8080
Running the app
go run main.go
INFO[0000] Initiated Callback=Callback
The http server is up and running, but we have not defined any endpoint. First stop the server by pressing ctrl c together and add the following function above main.
In main.go directly below router := mux.NewRouter().StrictSlash(true) add the following code
service.UpdateRoutes()
This code above will update the main route with the callback endpoint handler:
- /api/v1/ping: The handler’s endpoint
- callback: The handler’s function
- GET: Handler’s allowed methods
The full code now should look as the following code
package main
import (
"net/http"
"os"
"github.com/sirupsen/logrus"
"github.com/ulfox/callback/router"
)
// Global variables
var (
logger = logrus.New()
SRVER string = "GoWebapp"
)
func die(stage, msg string) {
logger.WithFields(logrus.Fields{
"Component": "Main",
"Stage": stage,
}).Error(msg)
os.Exit(1)
}
func main() {
logger.WithField("Server", SRVER).Infof("Initiated")
service, err := router.NewRouter(SRVER, logger)
if err != nil {
die("NewRouter", err.Error())
}
service.UpdateRoutes()
err = http.ListenAndServe(":8080", service.Router)
if err != nil {
die("ListenAndServe", err.Error())
}
}
Now let us run again the app and make a call
go run main.go
In a new terminal or from your browser make a request to localhost:8080/api/v1/ping
Expected server output
INFO[0000] Initiated Callback=Callback
INFO[0001] Callback=Callback Client="[::1]:34668" URL=/api/v1/ping
Client output
curl localhost:8080/api/v1/ping | jq
{
"message": "pong"
}
In the next post we will extend the http service to keep count for each client’s pong and save it into a local db.
Read next part: GoAPP-CRUD-Operations