Project Golang Webserver
Bash |
---|
| # Open terminal to desired project directory
cd ~/projects
# Make directory for the project
mkdir days-until-api
# Enter the directory
cd days-until-api
# Initialize go project
go mod init github.com/gerardrbentley/days-until-api
# Make file for entrypoint and basic integration testing
touch main.go main_test.go
# Make package for API handlers
mkdir handler
# Make file for web app code
touch handler/daysuntil.go handler/daysuntil_test.go
# Run tests
go test ./...
# Run Server
go run main.go
# Manually request endpoint (from another terminal)
curl "localhost:5000/daysuntil?date=2024-01-03"
# Build and run executable
go build main.go
./main
|
Main Entrypoint
main.go |
---|
| package main
import (
"log"
"net/http"
. "github.com/gerardrbentley/days-until-api/handler"
)
func setupHandlers() *http.ServeMux {
h := http.NewServeMux()
h.Handle("/daysuntil", DaysUntilHandler())
return h
}
func main() {
log.Println("Starting Up....")
h := setupHandlers()
log.Fatal(http.ListenAndServe(":5000", h))
}
|
Handler
handler/daysuntil.go |
---|
| package handler
import (
"encoding/json"
"log"
"net/http"
"time"
)
type DaysUntilError struct {
Error string `json:"error"`
}
type DaysUntilResponse struct {
Days int `json:"days"`
}
func DaysUntilHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
rawDate := r.URL.Query().Get("date")
layout := "2006-01-02"
requestedDate, err := time.Parse(layout, rawDate)
if err != nil {
log.Println(err.Error())
w.WriteHeader(http.StatusBadRequest)
p := DaysUntilError{Error: "not a valid date in format YYYY-MM-DD"}
if err := json.NewEncoder(w).Encode(p); err != nil {
log.Println(err.Error())
w.WriteHeader(http.StatusInternalServerError)
}
return
}
now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
difference := requestedDate.Sub(today)
numDays := int(difference.Hours() / 24)
p := DaysUntilResponse{Days: numDays}
if err := json.NewEncoder(w).Encode(p); err != nil {
log.Println(err.Error())
w.WriteHeader(http.StatusInternalServerError)
}
})
}
|
Happy Path Integration Test
main_test.go |
---|
| package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httptest"
"testing"
"time"
. "github.com/gerardrbentley/days-until-api/handler"
)
func TestDaysUntilRoute(t *testing.T) {
h := setupHandlers()
w := httptest.NewRecorder()
twoDaysFuture := time.Now().Add(time.Hour * 24 * 2).Format("2006-01-02")
req, _ := http.NewRequest("GET", fmt.Sprintf("/daysuntil?date=%s", twoDaysFuture), nil)
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Not OK %v", w.Code)
}
result := DaysUntilResponse{}
if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
log.Fatalln(err)
}
if result.Days != 2 {
t.Errorf("Not 2 days: %v", result.Days)
}
}
|
Happy Path Handler Test
handler/daysuntil_test.go |
---|
| package handler
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestDaysUntilHandler(t *testing.T) {
ctx := context.Background()
twoDaysFuture := time.Now().Add(time.Hour * 24 * 2).Format("2006-01-02")
req, _ := http.NewRequestWithContext(
ctx,
http.MethodPatch,
fmt.Sprintf("/daysuntil?date=%s", twoDaysFuture),
nil,
)
w := httptest.NewRecorder()
r := http.NewServeMux()
r.Handle("/daysuntil", DaysUntilHandler())
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Not OK %v", w.Code)
}
result := DaysUntilResponse{}
if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
log.Fatalln(err)
}
if result.Days != 2 {
t.Errorf("Not 2 days: %v", result.Days)
}
}
|
Gin Framework
Alternative to using standard library http servemux
Bash |
---|
| go get -u github.com/gin-gonic/gin
|
main.go |
---|
| package main
import "github.com/gin-gonic/gin"
func ginHandler(c *gin.Context) {
rawDate := c.Query("date")
c.JSON(http.StatusOK, gin.H{"same_date": rawDate})
}
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/daysuntil", ginHandler)
return r
}
func main() {
r := setupRouter()
r.Run(":5000")
}
|
Last update:
June 7, 2023
Created:
June 7, 2023