mirror of
https://github.com/twhite96/go-backend-api.git
synced 2025-01-30 22:49:15 +00:00
Initial commit
This commit is contained in:
commit
915e44b69a
46
.air.toml
Normal file
46
.air.toml
Normal file
@ -0,0 +1,46 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./main"
|
||||
cmd = "make build"
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with "go test -c"
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
tmp/
|
||||
|
||||
# IDE specific files
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# .env file
|
||||
.env
|
||||
|
||||
# Project build
|
||||
main
|
||||
*templ.go
|
||||
|
||||
# OS X generated file
|
||||
.DS_Store
|
||||
|
43
Makefile
Normal file
43
Makefile
Normal file
@ -0,0 +1,43 @@
|
||||
# Simple Makefile for a Go project
|
||||
|
||||
# Build the application
|
||||
all: build test
|
||||
|
||||
build:
|
||||
@echo "Building..."
|
||||
|
||||
|
||||
@go build -o main cmd/api/main.go
|
||||
|
||||
# Run the application
|
||||
run:
|
||||
@go run cmd/api/main.go
|
||||
|
||||
# Test the application
|
||||
test:
|
||||
@echo "Testing..."
|
||||
@go test ./... -v
|
||||
|
||||
# Clean the binary
|
||||
clean:
|
||||
@echo "Cleaning..."
|
||||
@rm -f main
|
||||
|
||||
# Live Reload
|
||||
watch:
|
||||
@if command -v air > /dev/null; then \
|
||||
air; \
|
||||
echo "Watching...";\
|
||||
else \
|
||||
read -p "Go's 'air' is not installed on your machine. Do you want to install it? [Y/n] " choice; \
|
||||
if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \
|
||||
go install github.com/air-verse/air@latest; \
|
||||
air; \
|
||||
echo "Watching...";\
|
||||
else \
|
||||
echo "You chose not to install air. Exiting..."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
.PHONY: all build run test clean watch
|
39
README.md
Normal file
39
README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Project go-calc
|
||||
|
||||
One Paragraph of project description goes here
|
||||
|
||||
## Getting Started
|
||||
|
||||
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.
|
||||
|
||||
## MakeFile
|
||||
|
||||
Run build make command with tests
|
||||
```bash
|
||||
make all
|
||||
```
|
||||
|
||||
Build the application
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
Run the application
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
Live reload the application:
|
||||
```bash
|
||||
make watch
|
||||
```
|
||||
|
||||
Run the test suite:
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
Clean up binary from the last build:
|
||||
```bash
|
||||
make clean
|
||||
```
|
57
cmd/api/main.go
Normal file
57
cmd/api/main.go
Normal file
@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"go-calc/internal/server"
|
||||
)
|
||||
|
||||
func gracefulShutdown(apiServer *http.Server, done chan bool) {
|
||||
// Create context that listens for the interrupt signal from the OS.
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
// Listen for the interrupt signal.
|
||||
<-ctx.Done()
|
||||
|
||||
log.Println("shutting down gracefully, press Ctrl+C again to force")
|
||||
|
||||
// The context is used to inform the server it has 5 seconds to finish
|
||||
// the request it is currently handling
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := apiServer.Shutdown(ctx); err != nil {
|
||||
log.Printf("Server forced to shutdown with error: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Server exiting")
|
||||
|
||||
// Notify the main goroutine that the shutdown is complete
|
||||
done <- true
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
server := server.NewServer()
|
||||
|
||||
// Create a done channel to signal when the shutdown is complete
|
||||
done := make(chan bool, 1)
|
||||
|
||||
// Run graceful shutdown in a separate goroutine
|
||||
go gracefulShutdown(server, done)
|
||||
|
||||
err := server.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
panic(fmt.Sprintf("http server error: %s", err))
|
||||
}
|
||||
|
||||
// Wait for the graceful shutdown to complete
|
||||
<-done
|
||||
log.Println("Graceful shutdown complete.")
|
||||
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module go-calc
|
||||
|
||||
go 1.23.2
|
||||
|
||||
require github.com/joho/godotenv v1.5.1
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
49
internal/server/routes.go
Normal file
49
internal/server/routes.go
Normal file
@ -0,0 +1,49 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *Server) RegisterRoutes() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// Register routes
|
||||
mux.HandleFunc("/", s.HelloWorldHandler)
|
||||
|
||||
// Wrap the mux with CORS middleware
|
||||
return s.corsMiddleware(mux)
|
||||
}
|
||||
|
||||
func (s *Server) corsMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Set CORS headers
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*") // Replace "*" with specific origins if needed
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, X-CSRF-Token")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "false") // Set to "true" if credentials are required
|
||||
|
||||
// Handle preflight OPTIONS requests
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
// Proceed with the next handler
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) {
|
||||
resp := map[string]string{"message": "Hello World"}
|
||||
jsonResp, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to marshal response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if _, err := w.Write(jsonResp); err != nil {
|
||||
log.Printf("Failed to write response: %v", err)
|
||||
}
|
||||
}
|
31
internal/server/routes_test.go
Normal file
31
internal/server/routes_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
s := &Server{}
|
||||
server := httptest.NewServer(http.HandlerFunc(s.HelloWorldHandler))
|
||||
defer server.Close()
|
||||
resp, err := http.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("error making request to server. Err: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// Assertions
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("expected status OK; got %v", resp.Status)
|
||||
}
|
||||
expected := "{\"message\":\"Hello World\"}"
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading response body. Err: %v", err)
|
||||
}
|
||||
if expected != string(body) {
|
||||
t.Errorf("expected response body to be %v; got %v", expected, string(body))
|
||||
}
|
||||
}
|
33
internal/server/server.go
Normal file
33
internal/server/server.go
Normal file
@ -0,0 +1,33 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
port int
|
||||
}
|
||||
|
||||
func NewServer() *http.Server {
|
||||
port, _ := strconv.Atoi(os.Getenv("PORT"))
|
||||
NewServer := &Server{
|
||||
port: port,
|
||||
}
|
||||
|
||||
// Declare Server config
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", NewServer.port),
|
||||
Handler: NewServer.RegisterRoutes(),
|
||||
IdleTimeout: time.Minute,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user