Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Ignore the Go binary (if it exists locally)
go-api-template

# Ignore dependency directories
vendor/
go.mod
go.sum

# Ignore IDE and editor files
.idea/
.vscode/
*.swp
*.swo

# Ignore build and test artifacts
bin/
pkg/
*.test
*.out

# Ignore log files
*.log

# Ignore Git files
.git/
.gitignore

# Ignore Docker files (if any)
Dockerfile
.dockerignore

# Ignore temporary files
tmp/
*.tmp

# Ignore environment-specific files
.env
.env.local
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# dont use "" in DATABASE_URI and CONFIG_PATH

DATABASE_URI=
CONFIG_PATH=config/local.yaml
60 changes: 60 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: CI/CD Pipeline

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20

- name: Run tests
run: go test -v ./...

build:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20

- name: Build the application
run: go build -o bin/go-api-template cmd/go-api-template/main.go

deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20

- name: Build the application
run: go build -o bin/go-api-template cmd/go-api-template/main.go

- name: Deploy to Heroku (example)
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: |
heroku container:login
heroku container:push web -a your-heroku-app-name
heroku container:release web -a your-heroku-app-name
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Use the official Golang image to create a build artifact.
FROM golang:1.23.4 as builder

# Set the working directory inside the container.
WORKDIR /app

# Copy the Go module files and download dependencies.
COPY go.mod go.sum ./
RUN go mod download

# Copy the rest of the application code.
COPY . .

# Build the Go application.
RUN CGO_ENABLED=0 GOOS=linux go build -o go-api-template .

# Use a minimal Alpine image for the final stage.
FROM alpine:latest

# Set the working directory.
WORKDIR /root/

# Copy the binary from the builder stage.
COPY --from=builder /app/go-api-template .

# Expose the port the app runs on.
EXPOSE 8080

# Command to run the application.
CMD ["./go-api-template"]
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,56 @@
# go-api-template
# Go API Template

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

This is a production-ready Go backend API template designed to kickstart your next project.

## Features

- **Multiple Database Support**: Easily switch between different databases (e.g., PostgreSQL, MySQL, SQLite).
- **Docker Integration**: Run the application and its dependencies in isolated containers.
- **Makefile**: Simplify common tasks like building, testing, and running the application.
- **Database Migrations**: Manage database schema changes with ease.
- **Configuration Management**: Use `local.yaml` for environment-specific configurations.
- **Modular Structure**: Organized into `models`, `database`, `handlers`, `middleware`, `repositories`, and `services`.
- **Testing System**: Includes a robust testing framework for unit and integration tests.

## Project Structure

## Prerequisites

- Go 1.23 or higher
- Docker and Docker Compose
- Make file

### 1. Create a New Repository

Click the **"Use this template"** button at the top right of this repository to create your own copy.

### 2. Configure the Application

Update the following files with your environment-specific settings:

- **`config/local.yaml`**: Add your application-specific configurations (e.g., server port, logging level).
- **`.env`**: Add your environment variables (e.g., database credentials, API keys).

### 3. Download Dependencies

`go mod download`

### 4. Run Your App

`make run`

### 5. Database Migrations

- To apply migrations: `make migrate-up`
- To rollback migrations: `make migrate-down`

## Contributing

Contributions are welcome! Please open an issue or submit a pull request for any improvements or bug fixes.

## License

This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details.

72 changes: 72 additions & 0 deletions cmd/go-api-template/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"context"

Check failure on line 4 in cmd/go-api-template/main.go

View workflow job for this annotation

GitHub Actions / test

cannot find package "context" in any of:
"log"
"log/slog"

Check failure on line 6 in cmd/go-api-template/main.go

View workflow job for this annotation

GitHub Actions / test

cannot find package "log/slog" in any of:
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/gauravst/go-api-template/internal/api/handlers"

Check failure on line 13 in cmd/go-api-template/main.go

View workflow job for this annotation

GitHub Actions / test

cannot find package "github.com/gauravst/go-api-template/internal/api/handlers" in any of:
"github.com/gauravst/go-api-template/internal/api/middleware"

Check failure on line 14 in cmd/go-api-template/main.go

View workflow job for this annotation

GitHub Actions / test

cannot find package "github.com/gauravst/go-api-template/internal/api/middleware" in any of:
"github.com/gauravst/go-api-template/internal/config"

Check failure on line 15 in cmd/go-api-template/main.go

View workflow job for this annotation

GitHub Actions / test

cannot find package "github.com/gauravst/go-api-template/internal/config" in any of:
"github.com/gauravst/go-api-template/internal/database"

Check failure on line 16 in cmd/go-api-template/main.go

View workflow job for this annotation

GitHub Actions / test

cannot find package "github.com/gauravst/go-api-template/internal/database" in any of:
"github.com/gauravst/go-api-template/internal/repositories"

Check failure on line 17 in cmd/go-api-template/main.go

View workflow job for this annotation

GitHub Actions / test

cannot find package "github.com/gauravst/go-api-template/internal/repositories" in any of:
"github.com/gauravst/go-api-template/internal/services"

Check failure on line 18 in cmd/go-api-template/main.go

View workflow job for this annotation

GitHub Actions / test

cannot find package "github.com/gauravst/go-api-template/internal/services" in any of:
)

func main() {
// load config
cfg := config.ConfigMustLoad()

// database setup
database.InitDB(cfg.DatabaseUri)
defer database.CloseDB()

//setup router
router := http.NewServeMux()

userRepo := repositories.NewUserRepository(database.DB)
userService := services.NewUserService(userRepo)

router.HandleFunc("GET /api/user", middleware.Auth(handlers.GetUser(userService)))
router.HandleFunc("POST /api/user", handlers.CreateUser(userService))
router.HandleFunc("PUT /api/user", middleware.Auth(handlers.UpdateUser(userService)))
router.HandleFunc("DELETE /api/user", middleware.Auth(handlers.DeleteUser(userService)))

// setup server
server := &http.Server{
Addr: cfg.Address,
Handler: router,
}

slog.Info("server started", slog.String("address", cfg.Address))

done := make(chan os.Signal, 1)

signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)

go func() {
err := server.ListenAndServe()
if err != nil {
log.Fatal("failed to start server")
}
}()

<-done

slog.Info("shutting down the server")

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

err := server.Shutdown(ctx)
if err != nil {
slog.Error("faild to Shutdown server", slog.String("error", err.Error()))
}

slog.Info("server Shutdown successfully")
}
4 changes: 4 additions & 0 deletions config/local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
env: "dev"
http_server:
address: "localhost:8080"
port : 8080
21 changes: 21 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module github.com/gauravst/go-api-template

go 1.23.4

require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/ilyakaznacheev/cleanenv v1.5.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
)
31 changes: 31 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=
69 changes: 69 additions & 0 deletions internal/api/handlers/user_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package handlers

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"

"github.com/gauravst/go-api-template/internal/models"
"github.com/gauravst/go-api-template/internal/services"
"github.com/gauravst/go-api-template/internal/utils/response"
"github.com/go-playground/validator/v10"
)

func CreateUser(userService services.UserService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

var user models.User

err := json.NewDecoder(r.Body).Decode(&user)
if errors.Is(err, io.EOF) {
response.WriteJson(w, http.StatusBadRequest, response.GeneralError(fmt.Errorf("empty body")))
return
}

if err != nil {
response.WriteJson(w, http.StatusBadRequest, response.GeneralError(err))
return
}

// Request validation
err = validator.New().Struct(user)
if err != nil {
validateErrs := err.(validator.ValidationErrors)
response.WriteJson(w, http.StatusBadRequest, response.ValidationError(validateErrs))
return
}

// call here services

err = userService.CreateUser(user)
if err != nil {
response.WriteJson(w, http.StatusInternalServerError, response.GeneralError(err))
return
}

// return response
response.WriteJson(w, http.StatusCreated, map[string]string{"success": "ok"})
}
}

func GetUser(userService services.UserService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

}
}

func UpdateUser(userService services.UserService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

}
}

func DeleteUser(userService services.UserService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

}
}
Loading
Loading