Building a Go Fiber API with Google Firestore
Introduction
In this post, we’ll extend our knowledge of Go Fiber by integrating it with Google Firestore. We’ll build a simple API that performs CRUD operations on a ‘users’ collection, demonstrating how to combine the speed of Fiber with the flexibility of Firestore.
Prerequisites
- Basic knowledge of Go programming and Fiber (refer to our previous post on Fiber routing)
- Google Cloud account with Firestore enabled
- Firestore credentials (service account key)
Setting Up the Project
First, let’s set up our project and import the necessary packages:
package main
import (
"context"
"log"
"os"
"cloud.google.com/go/firestore"
firebase "firebase.google.com/go"
"github.com/gofiber/fiber/v2"
"google.golang.org/api/option"
)
var firestoreClient *firestore.Client
func main() {
// Firestore setup will go here
app := fiber.New()
// Routes will be defined here
log.Fatal(app.Listen(":3000"))
}
Connecting to Firestore
Let’s set up our Firestore client:
func initFirestore() {
ctx := context.Background()
sa := option.WithCredentialsFile("path/to/your/service-account-key.json")
app, err := firebase.NewApp(ctx, nil, sa)
if err != nil {
log.Fatalf("Failed to create Firebase app: %v", err)
}
firestoreClient, err = app.Firestore(ctx)
if err != nil {
log.Fatalf("Failed to create Firestore client: %v", err)
}
}
Call initFirestore() at the beginning of your main() function.
Defining our User Model
Let’s define a simple User struct:
type User struct {
ID string `json:"id" firestore:"-"`
Name string `json:"name"`
Email string `json:"email"`
}
Implementing CRUD Operations
Now, let’s implement our CRUD operations using Fiber and Firestore:
Create a User
func createUser(c *fiber.Ctx) error {
user := new(User)
if err := c.BodyParser(user); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse JSON"})
}
doc, _, err := firestoreClient.Collection("users").Add(context.Background(), user)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create user"})
}
user.ID = doc.ID
return c.JSON(user)
}
Get a User
func getUser(c *fiber.Ctx) error {
id := c.Params("id")
doc, err := firestoreClient.Collection("users").Doc(id).Get(context.Background())
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
var user User
doc.DataTo(&user)
user.ID = doc.Ref.ID
return c.JSON(user)
}
Update a User
func updateUser(c *fiber.Ctx) error {
id := c.Params("id")
user := new(User)
if err := c.BodyParser(user); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse JSON"})
}
_, err := firestoreClient.Collection("users").Doc(id).Set(context.Background(), user)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to update user"})
}
user.ID = id
return c.JSON(user)
}
Delete a User
func deleteUser(c *fiber.Ctx) error {
id := c.Params("id")
_, err := firestoreClient.Collection("users").Doc(id).Delete(context.Background())
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to delete user"})
}
return c.SendString("User successfully deleted")
}
Setting Up Routes
Now let’s set up our routes in the main() function:
func main() {
initFirestore()
defer firestoreClient.Close()
app := fiber.New()
api := app.Group("/api")
users := api.Group("/users")
users.Post("/", createUser)
users.Get("/:id", getUser)
users.Put("/:id", updateUser)
users.Delete("/:id", deleteUser)
log.Fatal(app.Listen(":3000"))
}
Error Handling and Validation
In a production application, you’d want to add more robust error handling and input validation. Consider using a library like go-playground/validator for request validation.
Conclusion
By combining Go Fiber with Google Firestore, we’ve created a powerful, scalable API. This setup allows for rapid development and easy scaling, making it an excellent choice for modern web applications.
Canonical Example
Here’s a complete example that puts all of these concepts together:
package main
import (
"context"
"log"
"os"
"cloud.google.com/go/firestore"
firebase "firebase.google.com/go"
"github.com/gofiber/fiber/v2"
"google.golang.org/api/option"
)
var firestoreClient *firestore.Client
type User struct {
ID string `json:"id" firestore:"-"`
Name string `json:"name"`
Email string `json:"email"`
}
func initFirestore() {
ctx := context.Background()
sa := option.WithCredentialsFile("path/to/your/service-account-key.json")
app, err := firebase.NewApp(ctx, nil, sa)
if err != nil {
log.Fatalf("Failed to create Firebase app: %v", err)
}
firestoreClient, err = app.Firestore(ctx)
if err != nil {
log.Fatalf("Failed to create Firestore client: %v", err)
}
}
func createUser(c *fiber.Ctx) error {
user := new(User)
if err := c.BodyParser(user); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse JSON"})
}
doc, _, err := firestoreClient.Collection("users").Add(context.Background(), user)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create user"})
}
user.ID = doc.ID
return c.JSON(user)
}
func getUser(c *fiber.Ctx) error {
id := c.Params("id")
doc, err := firestoreClient.Collection("users").Doc(id).Get(context.Background())
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
var user User
doc.DataTo(&user)
user.ID = doc.Ref.ID
return c.JSON(user)
}
func updateUser(c *fiber.Ctx) error {
id := c.Params("id")
user := new(User)
if err := c.BodyParser(user); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse JSON"})
}
_, err := firestoreClient.Collection("users").Doc(id).Set(context.Background(), user)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to update user"})
}
user.ID = id
return c.JSON(user)
}
func deleteUser(c *fiber.Ctx) error {
id := c.Params("id")
_, err := firestoreClient.Collection("users").Doc(id).Delete(context.Background())
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to delete user"})
}
return c.SendString("User successfully deleted")
}
func main() {
initFirestore()
defer firestoreClient.Close()
app := fiber.New()
api := app.Group("/api")
users := api.Group("/users")
users.Post("/", createUser)
users.Get("/:id", getUser)
users.Put("/:id", updateUser)
users.Delete("/:id", deleteUser)
log.Fatal(app.Listen(":3000"))
}
This example provides a complete API for managing users, storing their data in Firestore. Remember to replace "path/to/your/service-account-key.json" with the actual path to your Firestore service account key.
In a production environment, you’d want to add authentication, more comprehensive error handling, and perhaps some caching mechanisms to optimize performance.