Creating a High-Performance Furniture Website Backend with Go's Standard Library
Harness the power of Go's standard library to create a robust web backend for a furniture store.
Introduction
Go, often referred to as Golang, is renowned for its simplicity, performance, and powerful standard library. While many web applications leverage frameworks like Gin or Echo, Go's standard library provides all the essential tools to build a fully functional web backend. In this blog post, we'll explore how to create an advanced furniture web backend using only Go's standard library.
Table of Contents
Prerequisites
Basic understanding of Go programming language.
Go installed on your machine (version 1.16 or later recommended).
Familiarity with HTTP concepts and RESTful API design.
Project Structure
For simplicity, we'll keep all our code in a single main.go
file. In a production environment, you would organize your code into packages and files for better maintainability.
furniture-backend/
└── main.go
Defining the Data Model
First, we define a Furniture
struct to represent our furniture items.
type Furniture struct {
ID int `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Material string `json:"material"`
Price float64 `json:"price"`
}
The struct fields are tagged with json
annotations to facilitate JSON serialization and deserialization.
Setting Up the In-Memory Data Store
We'll use a map to store our furniture items and a mutex to handle concurrent access.
var (
furnitureStore = make(map[int]Furniture)
idCounter = 1
storeMutex sync.Mutex
)
furnitureStore
: Holds the furniture items with their IDs as keys.idCounter
: Generates unique IDs for new furniture items.storeMutex
: Ensures thread-safe operations on the data store.
Implementing the HTTP Handlers
Retrieving All Furniture Items
func getFurnitures(w http.ResponseWriter, r *http.Request) {
storeMutex.Lock()
defer storeMutex.Unlock()
var furnitures []Furniture
for _, item := range furnitureStore {
furnitures = append(furnitures, item)
}
jsonResponse(w, furnitures, http.StatusOK)
}
Locks the store to prevent concurrent access issues.
Collects all furniture items into a slice.
Sends the slice as a JSON response with a 200 OK status.
Getting a Furniture Item by ID
func getFurnitureByID(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Query().Get("id")
if idStr == "" {
http.Error(w, "Missing 'id' query parameter", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid 'id' parameter", http.StatusBadRequest)
return
}
storeMutex.Lock()
furniture, exists := furnitureStore[id]
storeMutex.Unlock()
if !exists {
http.Error(w, "Furniture not found", http.StatusNotFound)
return
}
jsonResponse(w, furniture, http.StatusOK)
}
Retrieves the
id
from the query parameters.Validates the
id
and fetches the corresponding furniture item.Returns the item as a JSON response or an error if not found.
Creating a New Furniture Item
func createFurniture(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var newFurniture Furniture
if err := json.NewDecoder(r.Body).Decode(&newFurniture); err != nil {
http.Error(w, "Invalid JSON payload", http.StatusBadRequest)
return
}
storeMutex.Lock()
newFurniture.ID = idCounter
furnitureStore[idCounter] = newFurniture
idCounter++
storeMutex.Unlock()
jsonResponse(w, newFurniture, http.StatusCreated)
}
Checks that the request method is POST.
Decodes the JSON payload into a
Furniture
struct.Assigns a new ID and stores the item.
Returns the created item with a 201 Created status.
Updating an Existing Furniture Item
func updateFurniture(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var updatedFurniture Furniture
if err := json.NewDecoder(r.Body).Decode(&updatedFurniture); err != nil {
http.Error(w, "Invalid JSON payload", http.StatusBadRequest)
return
}
storeMutex.Lock()
defer storeMutex.Unlock()
if _, exists := furnitureStore[updatedFurniture.ID]; !exists {
http.Error(w, "Furniture not found", http.StatusNotFound)
return
}
furnitureStore[updatedFurniture.ID] = updatedFurniture
jsonResponse(w, updatedFurniture, http.StatusOK)
}
Ensures the request method is PUT.
Updates the existing furniture item if it exists.
Returns the updated item with a 200 OK status.
Deleting a Furniture Item
func deleteFurniture(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
idStr := r.URL.Query().Get("id")
if idStr == "" {
http.Error(w, "Missing 'id' query parameter", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid 'id' parameter", http.StatusBadRequest)
return
}
storeMutex.Lock()
defer storeMutex.Unlock()
if _, exists := furnitureStore[id]; !exists {
http.Error(w, "Furniture not found", http.StatusNotFound)
return
}
delete(furnitureStore, id)
w.WriteHeader(http.StatusNoContent)
}
Verifies the request method is DELETE.
Deletes the specified furniture item if it exists.
Returns a 204 No Content status on success.
Starting the HTTP Server
func main() {
// Initial data
furnitureStore[1] = Furniture{ID: 1, Name: "Armchair", Type: "Seating", Material: "Leather", Price: 249.99}
furnitureStore[2] = Furniture{ID: 2, Name: "Dining Table", Type: "Table", Material: "Wood", Price: 549.99}
idCounter = 3
// Route handlers
http.HandleFunc("/furnitures", getFurnitures)
http.HandleFunc("/furnitures/get", getFurnitureByID)
http.HandleFunc("/furnitures/create", createFurniture)
http.HandleFunc("/furnitures/update", updateFurniture)
http.HandleFunc("/furnitures/delete", deleteFurniture)
// Start the server
log.Println("Starting server on :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Initializes the data store with sample furniture items.
Registers the HTTP handlers with their respective routes.
Starts the HTTP server on port 8080.
Testing the API
1. Get All Furnitures
Request:
GET http://localhost:8080/furnitures
Response:
[
{
"id": 1,
"name": "Armchair",
"type": "Seating",
"material": "Leather",
"price": 249.99
},
{
"id": 2,
"name": "Dining Table",
"type": "Table",
"material": "Wood",
"price": 549.99
}
]
2. Get Furniture by ID
Request:
GET http://localhost:8080/furnitures/get?id=1
Response:
{
"id": 1,
"name": "Armchair",
"type": "Seating",
"material": "Leather",
"price": 249.99
}
3. Create New Furniture
Request:
POST http://localhost:8080/furnitures/create
Content-Type: application/json
{
"name": "Bookshelf",
"type": "Storage",
"material": "Wood",
"price": 199.99
}
Response:
{
"id": 3,
"name": "Bookshelf",
"type": "Storage",
"material": "Wood",
"price": 199.99
}
4. Update Furniture
Request:
PUT http://localhost:8080/furnitures/update
Content-Type: application/json
{
"id": 2,
"name": "Dining Table",
"type": "Table",
"material": "Glass",
"price": 599.99
}
Response:
{
"id": 2,
"name": "Dining Table",
"type": "Table",
"material": "Glass",
"price": 599.99
}
5. Delete Furniture
Request:
DELETE http://localhost:8080/furnitures/delete?id=1
Response:
Status Code: 204 No Content
Advanced Features and Considerations
While the basic CRUD operations are functional, there are several advanced features and best practices to consider for a production-ready application.
Data Persistence
Using an in-memory store means all data is lost when the application stops. Integrate a database to persist data:
SQL Databases: Use
database/sql
with drivers likepq
for PostgreSQL ormysql
for MySQL.NoSQL Databases: Interface with databases like MongoDB using appropriate drivers.
Input Validation
Ensure that all incoming data meets required criteria to prevent errors and security vulnerabilities.
func validateFurniture(f Furniture) error {
if f.Name == "" || f.Type == "" || f.Material == "" {
return errors.New("Name, Type, and Material are required")
}
if f.Price <= 0 {
return errors.New("Price must be greater than zero")
}
return nil
}
Use this validation function before processing data in your handlers.
Authentication and Authorization
Implement authentication mechanisms to secure your API:
Token-Based Authentication: Use JWT tokens to authenticate users.
API Keys: Assign API keys to clients for access control.
Middleware
Create middleware functions to handle cross-cutting concerns:
Logging Middleware: Log requests and responses for monitoring.
CORS Middleware: Handle Cross-Origin Resource Sharing if your API will be accessed from browsers.
Recovery Middleware: Gracefully handle panics and prevent server crashes.
Pagination and Filtering
Enhance the getFurnitures
handler to support pagination and filtering:
func getFurnitures(w http.ResponseWriter, r *http.Request) {
// Parse query parameters for pagination and filtering
}
Error Handling
Standardize your error responses to provide consistent feedback to clients.
type ErrorResponse struct {
Error string `json:"error"`
}
Use this struct to send error messages in JSON format.
Conclusion
Building a web backend using Go's standard library is both straightforward and efficient. By leveraging the powerful packages provided out-of-the-box, you can create robust and high-performance applications without external dependencies.
This furniture web backend serves as a foundational example. From here, you can expand its capabilities by integrating databases, implementing security features, and enhancing user experience.
Happy Coding!
Subscribe to my newsletter
Read articles from Sundaram Kumar Jha directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sundaram Kumar Jha
Sundaram Kumar Jha
I Like Building Cloud Native Stuff , the microservices, backends, distributed systemsand cloud native tools using Golang