JB logo

Command Palette

Search for a command to run...

yOUTUBE
Blog
Next

Building a Complete Employee Management System in Go

A comprehensive step-by-step tutorial for learning Go fundamentals by building a real-world REST API with CRUD operations, SQLite database integration, and Excel file import functionality.

Complete Go Course: Excel to Employee API

A practical step-by-step guide to learning Go by building a real-world project.


Part 1: Go Basics - Essential Concepts

1.1 Variables and Types

Go is statically typed. Here are the most common types you'll use daily:

// Variable declaration
var name string = "John"
age := 25  // Short declaration (type inferred)
 
// Common types
var (
    isActive bool = true
    count int = 10
    price float64 = 99.99
    message string = "Hello"
)

1.2 Structs

Structs are Go's way of creating custom types:

type Employee struct {
    ID       string `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email"`
    Position string `json:"position"`
}
 
// Creating an instance
emp := Employee{
    ID:       "1",
    Name:     "John Doe",
    Email:    "john@example.com",
    Position: "Developer",
}

1.3 Functions

// Basic function
func greet(name string) string {
    return "Hello, " + name
}
 
// Multiple return values (common in Go for error handling)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("cannot divide by zero")
    }
    return a / b, nil
}

1.4 Error Handling

Go uses explicit error handling (no exceptions):

result, err := divide(10, 0)
if err != nil {
    fmt.Println("Error:", err)
    return
}
fmt.Println("Result:", result)

1.5 Slices (Dynamic Arrays)

// Create a slice
employees := []Employee{}
 
// Append to slice
emp := Employee{ID: "1", Name: "John"}
employees = append(employees, emp)
 
// Loop through slice
for i, emp := range employees {
    fmt.Printf("Index %d: %s\n", i, emp.Name)
}

1.6 Maps (Key-Value Pairs)

// Create a map
userRoles := make(map[string]string)
userRoles["john@example.com"] = "admin"
 
// Access value
role := userRoles["john@example.com"]

1.7 Pointers

// Pointers allow you to pass references
func updateName(emp *Employee) {
    emp.Name = "Updated Name"
}
 
employee := Employee{Name: "John"}
updateName(&employee)  // Pass pointer with &
fmt.Println(employee.Name)  // "Updated Name"

1.8 Packages and Imports

package main  // Every Go file belongs to a package
 
import (
    "fmt"           // Standard library
    "database/sql"  // SQL package
    _ "github.com/mattn/go-sqlite3"  // Third-party package
)

Part 2: Building the Employee API with CRUD

Step 1: Project Setup

Create your project structure:

employee-api/
├── main.go
├── database.go
├── handlers.go
└── go.mod

Initialize the Go module:

go mod init employee-api

Step 2: Install Dependencies

go get github.com/mattn/go-sqlite3
go get github.com/gorilla/mux

Step 3: Database Setup (database.go)

package main
 
import (
    "database/sql"
    "log"
    _ "github.com/mattn/go-sqlite3"
)
 
var db *sql.DB
 
func InitDB() {
    var err error
    db, err = sql.Open("sqlite3", "./employees.db")
    if err != nil {
        log.Fatal(err)
    }
 
    // Create table
    createTableSQL := `CREATE TABLE IF NOT EXISTS employees (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        email TEXT NOT NULL,
        position TEXT NOT NULL
    );`
 
    _, err = db.Exec(createTableSQL)
    if err != nil {
        log.Fatal(err)
    }
}

Step 4: API Handlers (handlers.go)

package main
 
import (
    "encoding/json"
    "net/http"
    "github.com/google/uuid"
    "github.com/gorilla/mux"
)
 
type Employee struct {
    ID       string `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email"`
    Position string `json:"position"`
}
 
// CREATE - Add new employee
func CreateEmployee(w http.ResponseWriter, r *http.Request) {
    var emp Employee
    json.NewDecoder(r.Body).Decode(&emp)
 
    // Generate ID if not provided
    if emp.ID == "" {
        emp.ID = uuid.New().String()
    }
 
    _, err := db.Exec("INSERT INTO employees (id, name, email, position) VALUES (?, ?, ?, ?)",
        emp.ID, emp.Name, emp.Email, emp.Position)
 
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
 
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(emp)
}
 
// READ - Get all employees
func GetEmployees(w http.ResponseWriter, r *http.Request) {
    rows, err := db.Query("SELECT id, name, email, position FROM employees")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()
 
    var employees []Employee
    for rows.Next() {
        var emp Employee
        rows.Scan(&emp.ID, &emp.Name, &emp.Email, &emp.Position)
        employees = append(employees, emp)
    }
 
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(employees)
}
 
// READ - Get single employee by ID
func GetEmployee(w http.ResponseWriter, r *http.Request) {
    params := mux.Vars(r)
    var emp Employee
 
    err := db.QueryRow("SELECT id, name, email, position FROM employees WHERE id = ?", params["id"]).
        Scan(&emp.ID, &emp.Name, &emp.Email, &emp.Position)
 
    if err != nil {
        http.Error(w, "Employee not found", http.StatusNotFound)
        return
    }
 
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(emp)
}
 
// UPDATE - Update employee
func UpdateEmployee(w http.ResponseWriter, r *http.Request) {
    params := mux.Vars(r)
    var emp Employee
    json.NewDecoder(r.Body).Decode(&emp)
 
    _, err := db.Exec("UPDATE employees SET name=?, email=?, position=? WHERE id=?",
        emp.Name, emp.Email, emp.Position, params["id"])
 
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
 
    emp.ID = params["id"]
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(emp)
}
 
// DELETE - Delete employee
func DeleteEmployee(w http.ResponseWriter, r *http.Request) {
    params := mux.Vars(r)
 
    _, err := db.Exec("DELETE FROM employees WHERE id=?", params["id"])
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
 
    w.WriteHeader(http.StatusNoContent)
}

Step 5: Main Application (main.go)

package main
 
import (
    "fmt"
    "log"
    "net/http"
    "github.com/gorilla/mux"
)
 
func main() {
    // Initialize database
    InitDB()
    defer db.Close()
 
    // Setup router
    router := mux.NewRouter()
 
    // Define routes
    router.HandleFunc("/employees", GetEmployees).Methods("GET")
    router.HandleFunc("/employees/{id}", GetEmployee).Methods("GET")
    router.HandleFunc("/employees", CreateEmployee).Methods("POST")
    router.HandleFunc("/employees/{id}", UpdateEmployee).Methods("PUT")
    router.HandleFunc("/employees/{id}", DeleteEmployee).Methods("DELETE")
 
    // Start server
    fmt.Println("Server starting on port 8080...")
    log.Fatal(http.ListenAndServe(":8080", router))
}

Step 6: Run and Test the API

# Run the application
go run .
 
# Test with curl
# CREATE
curl -X POST http://localhost:8080/employees \
  -H "Content-Type: application/json" \
  -d '{"name":"John Doe","email":"john@example.com","position":"Developer"}'
 
# READ ALL
curl http://localhost:8080/employees
 
# READ ONE
curl http://localhost:8080/employees/{id}
 
# UPDATE
curl -X PUT http://localhost:8080/employees/{id} \
  -H "Content-Type: application/json" \
  -d '{"name":"John Updated","email":"john@example.com","position":"Senior Developer"}'
 
# DELETE
curl -X DELETE http://localhost:8080/employees/{id}

Part 3: Excel Import Feature

Step 1: Install Excel Library

go get github.com/xuri/excelize/v2

Step 2: Create Excel Import Function

Create a new file excel.go:

package main
 
import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "github.com/google/uuid"
    "github.com/xuri/excelize/v2"
)
 
type ExcelEmployee struct {
    Name     string `json:"name"`
    Email    string `json:"email"`
    Position string `json:"position"`
}
 
func ImportEmployeesFromExcel(filename string) error {
    // 1. Open Excel file
    f, err := excelize.OpenFile(filename)
    if err != nil {
        return fmt.Errorf("error opening file: %v", err)
    }
    defer f.Close()
 
    // 2. Read rows from first sheet
    sheetName := f.GetSheetName(0)
    rows, err := f.GetRows(sheetName)
    if err != nil {
        return fmt.Errorf("error reading rows: %v", err)
    }
 
    // 3. Convert to slice of structs
    var employees []Employee
 
    // Skip header row (index 0)
    for i, row := range rows {
        if i == 0 {
            continue // Skip header
        }
 
        // Ensure we have all columns
        if len(row) < 3 {
            continue
        }
 
        // Create employee with generated ID
        emp := Employee{
            ID:       uuid.New().String(), // Generate new UUID
            Name:     row[0],
            Email:    row[1],
            Position: row[2],
        }
 
        employees = append(employees, emp)
    }
 
    // 4. Convert to JSON for viewing (optional)
    jsonData, err := json.MarshalIndent(employees, "", "  ")
    if err != nil {
        return fmt.Errorf("error converting to JSON: %v", err)
    }
    fmt.Println("Extracted Data:")
    fmt.Println(string(jsonData))
 
    // 5. Loop through and save each employee to database
    for i, emp := range employees {
        err := saveEmployee(emp)
        if err != nil {
            fmt.Printf("Error saving employee %d (%s): %v\n", i+1, emp.Name, err)
            continue
        }
        fmt.Printf("✓ Saved employee %d: %s (ID: %s)\n", i+1, emp.Name, emp.ID)
    }
 
    fmt.Printf("\nSuccessfully imported %d employees!\n", len(employees))
    return nil
}
 
func saveEmployee(emp Employee) error {
    _, err := db.Exec(
        "INSERT INTO employees (id, name, email, position) VALUES (?, ?, ?, ?)",
        emp.ID, emp.Name, emp.Email, emp.Position,
    )
    return err
}

Step 3: Add Import Command to Main

Update main.go:

package main
 
import (
    "fmt"
    "log"
    "net/http"
    "os"
    "github.com/gorilla/mux"
)
 
func main() {
    // Initialize database
    InitDB()
    defer db.Close()
 
    // Check for import command
    if len(os.Args) > 1 && os.Args[1] == "import" {
        if len(os.Args) < 3 {
            fmt.Println("Usage: go run . import <excel-file>")
            return
        }
 
        err := ImportEmployeesFromExcel(os.Args[2])
        if err != nil {
            log.Fatal(err)
        }
        return
    }
 
    // Setup router
    router := mux.NewRouter()
 
    // Define routes
    router.HandleFunc("/employees", GetEmployees).Methods("GET")
    router.HandleFunc("/employees/{id}", GetEmployee).Methods("GET")
    router.HandleFunc("/employees", CreateEmployee).Methods("POST")
    router.HandleFunc("/employees/{id}", UpdateEmployee).Methods("PUT")
    router.HandleFunc("/employees/{id}", DeleteEmployee).Methods("DELETE")
 
    // Start server
    fmt.Println("Server starting on port 8080...")
    log.Fatal(http.ListenAndServe(":8080", router))
}

Step 4: Create Sample Excel File

Create employees.xlsx with this structure:

NameEmailPosition
John Doejohn@example.comDeveloper
Jane Smithjane@example.comManager
Bob Johnsonbob@example.comDesigner

Step 5: Run the Import

# Import employees from Excel
go run . import employees.xlsx
 
# Start the API server
go run .
 
# Verify imported data
curl http://localhost:8080/employees

Complete Project Structure

employee-api/
├── main.go          # Entry point and routing
├── database.go      # Database initialization
├── handlers.go      # CRUD API handlers
├── excel.go         # Excel import functionality
├── employees.xlsx   # Sample Excel file
├── employees.db     # SQLite database (auto-created)
└── go.mod          # Dependencies

Key Concepts Review

Variables & Types - Declaring and using Go data types
Structs - Creating custom data structures
Functions - Writing reusable code blocks
Error Handling - Go's explicit error checking
Slices - Working with dynamic arrays
Loops - Using for and range
Pointers - Passing references
Packages - Organizing code
HTTP APIs - Building REST endpoints
Database - SQL operations
JSON - Encoding and decoding
File Processing - Reading Excel files


Next Steps

  1. Add validation for email format
  2. Add duplicate email checking
  3. Implement pagination for GET all employees
  4. Add authentication middleware
  5. Create unit tests
  6. Deploy to production

Congratulations! You've built a complete Go application with API and Excel integration! 🎉