Skip to main content
The Go React Scaffold is organized as a monorepo with separate frontend and backend directories. This guide explains how each part is structured and how they work together.

Directory Overview

go-react-scaffold/
├── frontend/           # React + TypeScript + Vite + TailwindCSS
├── backend/            # Go + Echo + MongoDB
├── README.md           # Project documentation
└── AGENTS.md           # Coding guidelines

Frontend Structure

The frontend is a modern React application built with TypeScript, Vite, and TailwindCSS.

Frontend Directory Layout

frontend/
├── src/
│   ├── App.tsx              # Main application component
│   ├── main.tsx             # Application entry point
│   ├── index.css            # Tailwind CSS directives
│   ├── App.css              # Component-specific styles
│   ├── vite-env.d.ts        # Vite type declarations
│   └── assets/              # Static assets (images, icons)
├── public/                   # Public static files
├── package.json              # Dependencies and scripts
├── tsconfig.json             # TypeScript configuration
├── tsconfig.app.json         # App-specific TypeScript config
├── tsconfig.node.json        # Node-specific TypeScript config
├── vite.config.ts            # Vite build configuration
├── tailwind.config.js        # TailwindCSS configuration
├── postcss.config.js         # PostCSS configuration
└── eslint.config.js          # ESLint configuration

Key Frontend Files

The entry point that mounts the React application to the DOM:
frontend/src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
  • Uses React 18’s createRoot API
  • Wrapped in StrictMode for development warnings
  • Imports global styles (including Tailwind)
The root component of your application:
frontend/src/App.tsx
import { useState } from 'react'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
      </div>
    </>
  )
}

export default App
This is where you’ll build your UI. Replace this with your application logic.
Includes Tailwind CSS directives:
frontend/src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
This enables TailwindCSS utility classes throughout your application.

Frontend Technologies

React 18

Modern React with hooks, concurrent features, and automatic batching

TypeScript

Type-safe development with strict mode enabled for better code quality

Vite

Lightning-fast dev server with HMR and optimized production builds

TailwindCSS

Utility-first CSS framework for rapid UI development

Backend Structure

The backend is a RESTful API built with Go, Echo framework, and MongoDB.

Backend Directory Layout

backend/
├── main.go                   # Application entry point & routing
├── auth/                     # Authentication module
│   ├── auth.go              # JWT token generation and validation
│   ├── configs.go           # Auth configuration
│   ├── controller.go        # Business logic (login, registration)
│   └── routes.go            # HTTP handlers
├── users/                    # User module
│   └── model.go             # User data model and DB operations
├── configs/                  # Configuration module
│   ├── db.go                # MongoDB connection and operations
│   └── env.go               # Environment variable helpers
├── integrations/             # External service integrations
│   ├── paypack.go           # Payment integration
│   ├── telegram-bot.go      # Telegram notifications
│   └── useplunk.go          # Email service integration
├── utils/                    # Utility functions
│   └── emails.go            # Email helpers
├── .env.example              # Environment variables template
├── .air.toml                 # Air live reload configuration
├── Makefile                  # Build and dev commands
├── go.mod                    # Go module dependencies
└── go.sum                    # Dependency checksums

Key Backend Files

The main file that initializes the server, middleware, and routes:
backend/main.go
package main

import (
  "fmt"
  "log"
  _ "time/tzdata"

  "backend/auth"
  "backend/configs"

  _ "github.com/joho/godotenv/autoload"
  "github.com/labstack/echo/v4"
  "github.com/labstack/echo/v4/middleware"
)

func main() {
  var app = echo.New()
  app.Use(middleware.Logger())
  app.Use(middleware.Recover())
  app.Use(middleware.CORS())

  configs.ConnectDB()

  // public routes
  app.POST("/register", auth.HandleUserRegistration)
  app.POST("/login", auth.HandleUserLogin)

  port := fmt.Sprintf(":%s", configs.EnvPort())
  log.Fatal(app.Start(port))
}
Key Features:
  • Auto-loads .env file via godotenv/autoload
  • Configures Echo middleware: Logger, Recover, CORS
  • Connects to MongoDB on startup
  • Defines public API routes
Defines the User struct and database operations:
backend/users/model.go
package users

import (
  "context"
  "time"
  "backend/configs"
  "go.mongodb.org/mongo-driver/bson"
)

type User struct {
  ID          string    `json:"id" bson:"_id"`
  Email       string    `json:"email" bson:"email"`
  Phone       string    `json:"phone" bson:"phone"`
  Password    []byte    `json:"password" bson:"password"`
  Role        string    `json:"role" bson:"role"`
  Status      string    `json:"status" bson:"status"`
  AccountType string    `json:"account_type" bson:"account_type"`
  CreatedAt   time.Time `json:"created_at" bson:"created_at"`
  UpdatedAt   time.Time `json:"updated_at" bson:"updated_at"`
}

func (u *User) CreateNewUser() error {
  _, err := configs.StoreRequestInDb(*u, "users")
  if err != nil {
    return err
  }
  return nil
}

func GetUserByEmail(email string) (User, error) {
  var user User
  err := configs.MI.DB.Collection("users").FindOne(
    context.TODO(), 
    bson.M{"email": email},
  ).Decode(&user)
  if err != nil {
    return user, err
  }
  return user, nil
}
Features:
  • Struct tags for JSON and BSON serialization
  • Method receiver for creating users
  • Helper functions for querying by email/phone
Contains the business logic for user registration and login:
backend/auth/controller.go
package auth

import (
  "fmt"
  "time"
  "backend/users"
  "github.com/google/uuid"
  "golang.org/x/crypto/bcrypt"
)

func processUserLogin(email, password string) (string, bool, error) {
  user, err := users.GetUserByEmail(email)
  if err != nil {
    return "", false, err
  }
  if user.Status != "active" {
    return "", false, fmt.Errorf("user is not active")
  }
  errr := bcrypt.CompareHashAndPassword(user.Password, []byte(password))
  if errr != nil {
    return "", false, fmt.Errorf("the password provided is not valid")
  }

  return user.ID, true, nil
}

func createNewUser(email string, password []byte) (users.User, error) {
  uid := uuid.New().String()
  hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 14)
  if err != nil {
    return users.User{}, err
  }
  user := users.User{
    Email:     email,
    Status:    "pending",
    Password:  hashedPassword,
    ID:        uid,
    CreatedAt: time.Now(),
  }
  err = user.CreateNewUser()
  if err != nil {
    return users.User{}, err
  }
  return user, nil
}
Security Features:
  • Bcrypt password hashing with cost factor 14
  • User status validation
  • UUID generation for user IDs
Handles JWT token generation and cookie management:
backend/auth/auth.go
package auth

import (
  "backend/configs"
  "backend/users"
  "net/http"
  "time"
  "github.com/golang-jwt/jwt/v5"
  "github.com/labstack/echo/v4"
)

type Claims struct {
  Name   string `json:"name"`
  UserId string `json:"user_id"`
  jwt.Claims
}

func GenerateTokensAndSetCookies(user *users.User, c echo.Context) (string, error) {
  accessToken, exp, err := generateAccessToken(user)
  if err != nil {
    return "", err
  }

  setTokenCookie(accessTokenCookieName, accessToken, exp, c)
  setUserCookie(user, exp, c)

  return accessToken, nil
}

func generateToken(user *users.User, expirationTime time.Time, secret []byte) (string, time.Time, error) {
  claims := &Claims{
    UserId: user.ID,
    Claims: jwt.MapClaims{
      "exp": expirationTime.Unix(),
    },
  }

  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  tokenString, err := token.SignedString(secret)
  if err != nil {
    return "", time.Now(), err
  }

  return tokenString, expirationTime, nil
}
Features:
  • JWT tokens with 24-hour expiration
  • HTTP-only cookies for security
  • HS256 signing algorithm
MongoDB connection and helper functions:
backend/configs/db.go
package configs

import (
  "context"
  "fmt"
  "log"
  "go.mongodb.org/mongo-driver/bson"
  "go.mongodb.org/mongo-driver/mongo"
  "go.mongodb.org/mongo-driver/mongo/options"
)

type MongoInstance struct {
  Client *mongo.Client
  DB     *mongo.Database
}

var MI MongoInstance

func ConnectDB() {
  dbName := fmt.Sprintf("%s-backend", AppEnv())
  serverAPI := options.ServerAPI(options.ServerAPIVersion1)
  opts := options.Client().ApplyURI(EnvMongoURI()).SetServerAPIOptions(serverAPI)
  client, err := mongo.Connect(context.TODO(), opts)
  if err != nil {
    log.Fatalf(err.Error())
  }

  var result bson.M
  if err := client.Database(dbName).RunCommand(
    context.TODO(), 
    bson.D{{Key: "ping", Value: 1}},
  ).Decode(&result); err != nil {
    log.Fatalf(err.Error())
  }
  fmt.Println("Pinged your deployment. You successfully connected to MongoDB!")

  MI = MongoInstance{
    Client: client,
    DB:     client.Database(dbName),
  }
}
Features:
  • Global MongoInstance for database access
  • Environment-based database naming
  • Connection validation with ping

Backend Technologies

Go 1.21+

Modern Go with generics and improved performance

Echo v4

High-performance web framework with middleware support

MongoDB

NoSQL database with flexible document storage

JWT

Secure token-based authentication with bcrypt password hashing

Module Organization

The backend follows a module-based structure where each feature is organized into its own package:

Auth Module

Purpose: Handles user authentication and authorization Files:
  • auth.go: JWT token generation and cookie management
  • controller.go: Business logic for login and registration
  • routes.go: HTTP request handlers
  • configs.go: Authentication configuration
Responsibilities:
  • User registration with password hashing
  • User login with credentials validation
  • JWT token generation and validation
  • Session management via HTTP-only cookies

Users Module

Purpose: User data model and database operations Files:
  • model.go: User struct and CRUD operations
Responsibilities:
  • Define user data structure
  • Create new users in database
  • Query users by email, phone, or ID
  • Update user information

Configs Module

Purpose: Application configuration and database connectivity Files:
  • db.go: MongoDB connection and helper functions
  • env.go: Environment variable accessors
Responsibilities:
  • Establish MongoDB connection
  • Provide database helper functions (insert, update)
  • Load and expose environment variables
  • Manage application configuration

Integrations Module

Purpose: External service integrations Files:
  • paypack.go: Payment processing integration
  • telegram-bot.go: Telegram notifications
  • useplunk.go: Email service integration
Responsibilities:
  • Connect to third-party APIs
  • Handle payment processing
  • Send notifications and emails
These integrations are optional and can be removed if not needed.

How the Pieces Fit Together

Request Flow

Here’s how a typical authentication request flows through the application:
1

Client Request

Frontend sends POST request to /login with email and password
2

Echo Middleware

Request passes through middleware stack:
  • Logger: Records request details
  • Recover: Handles panics gracefully
  • CORS: Validates origin and sets headers
3

Route Handler

auth.HandleUserLogin in routes.go receives the request:
func HandleUserLogin(ctx echo.Context) error {
  email := ctx.FormValue("email")
  password := ctx.FormValue("password")
  token, status, err := processUserLogin(email, password)
  // ...
}
4

Business Logic

processUserLogin in controller.go validates credentials:
  • Queries user from MongoDB via users.GetUserByEmail
  • Checks user status
  • Compares hashed password using bcrypt
  • Returns user ID if successful
5

Response

Handler returns JSON response with token and status

Database Interaction

Data flows from application code to MongoDB:
Application Code

User Model (users/model.go)

Config Helpers (configs/db.go)

Mongo Driver (go.mongodb.org/mongo-driver)

MongoDB Database

Frontend-Backend Communication

The frontend and backend communicate via REST API:
React Component
      ↓ (fetch/axios)
HTTP Request

Echo Router

Auth Handler

HTTP Response (JSON)

React Component Updates

Environment Configuration

The application uses environment variables for configuration:
backend/.env
APP_ENV=development
MONGODB_URI=mongodb://localhost:27017
PORT=8080
SESSION_KEY=your-secret-jwt-key
REDIS_URL=redis://localhost:6379
PAYPACK_CLIENT_ID=
PAYPACK_CLIENT_SECRET=
USE_PLUNK=
TELEGRAM_BOT_ID=
TELEGRAM_CHAT_ID=
Core Variables:
  • APP_ENV: Environment (development/production) - affects database naming
  • MONGODB_URI: MongoDB connection string
  • PORT: Server port (default: 8080)
  • SESSION_KEY: Secret key for JWT signing (required)
Optional Variables (for integrations):
  • REDIS_URL: Redis cache connection
  • PAYPACK_*: Payment integration credentials
  • USE_PLUNK: Email service API key
  • TELEGRAM_*: Telegram bot credentials
Never commit the .env file to version control. Always use .env.example as a template.

Development Workflow

Adding a New Feature

1

Create Backend Endpoint

  1. Add a new handler in the appropriate module (or create a new module)
  2. Implement business logic in a controller function
  3. Register the route in main.go
2

Test with cURL

Test your endpoint using cURL or Postman before building the UI
3

Create Frontend Component

  1. Create a new React component in frontend/src/
  2. Make API calls using fetch or axios
  3. Style with TailwindCSS utility classes
4

Connect the Pieces

Update your App.tsx to include the new component

Example: Adding a Profile Endpoint

Backend (backend/main.go):
app.GET("/profile/:id", auth.HandleGetProfile)
Frontend (React component):
const response = await fetch(`http://localhost:8080/profile/${userId}`);
const profile = await response.json();

Build and Production

Frontend Build

npm run build
Creates an optimized production build in frontend/dist/:
  • Minified JavaScript and CSS
  • Tree-shaken dependencies
  • Optimized assets

Backend Build

make build
Compiles Go code into a single binary main:
  • Statically linked
  • No runtime dependencies (except MongoDB)
  • Cross-platform compatible

Next Steps

Quick Start

Get the project running locally in minutes

Add Authentication UI

Build login and registration forms in React

Create Custom Endpoints

Extend the API with your own routes and handlers

Deploy to Production

Learn how to deploy your full-stack application

Build docs developers (and LLMs) love