Creating a Simple Chat Application using Go and React
How to install and use libraries
Sure! Let’s break down the provided Go code into a structured explanation for the backend of our calorie tracker application.
Backend: Go with Gin and MongoDB
We’ll use the Go programming language with the Gin framework for our backend. MongoDB will serve as our database to store calorie entries.
Step 1: Models
The models
package defines the structure of our calorie entry data.
package models
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Entry represents a calorie entry in the database
type Entry struct {
ID primitive.ObjectID `bson:"id"`
Dish *string `json:"dish"`
Fat *float64 `json:"fat"`
Ingredients *string `json:"ingredients"`
Calories *string `json:"calories"`
}
This structure includes:
ID
: A unique identifier for the entry, generated by MongoDB.Dish
: The name of the dish.Fat
: The fat content of the dish.Ingredients
: The ingredients of the dish.Calories
: The calorie count of the dish.
Step 2: Database Connection
The routes
package handles the database connection.
package routes
import (
"context"
"fmt"
"log"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func DBinstance() *mongo.Client {
MongoDb := "mongodb://localhost:27017/caloriesdb"
client, err := mongo.NewClient(options.Client().ApplyURI(MongoDb))
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err = client.Connect(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Connected to MongoDB")
return client
}
var Client *mongo.Client = DBinstance()
func OpenCollection(client *mongo.Client, collectionName string) *mongo.Collection {
var collection *mongo.Collection = client.Database("caloriesdb").Collection(collectionName)
return collection
}
This code sets up the connection to a MongoDB database running locally on mongodb://localhost:27017
. The database is named caloriesdb
and contains a collection named calories
.
Step 3: CRUD Operations
The routes
package also defines the CRUD operations for our calorie entries.
package routes
import (
"context"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/your_username/your_project_name/models"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
var validate = validator.New()
var entryCollection *mongo.Collection = OpenCollection(Client, "calories")
func AddEntry(c *gin.Context) {
var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second)
var entry models.Entry
if err := c.BindJSON(&entry); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
validationErr := validate.Struct(entry)
if validationErr != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": validationErr.Error()})
fmt.Println(validationErr)
return
}
entry.ID = primitive.NewObjectID()
result, insertErr := entryCollection.InsertOne(ctx, entry)
if insertErr != nil {
msg := fmt.Sprintf("Entry was not created")
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
fmt.Println(insertErr)
return
}
defer cancel()
c.JSON(http.StatusOK, result)
}
func GetEntries(c *gin.Context) {
var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second)
var entries []bson.M
cursor, err := entryCollection.Find(ctx, bson.M{})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
if err = cursor.All(ctx, &entries); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
defer cancel()
fmt.Println(entries)
c.JSON(http.StatusOK, entries)
}
func GetEntriesByIngredient(c *gin.Context) {
ingredient := c.Params.ByName("ingredient")
var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second)
var entries []bson.M
cursor, err := entryCollection.Find(ctx, bson.M{"ingredients": ingredient})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
if err = cursor.All(ctx, &entries); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
defer cancel()
fmt.Println(entries)
c.JSON(http.StatusOK, entries)
}
func GetEntryById(c *gin.Context) {
EntryID := c.Params.ByName("id")
docID, _ := primitive.ObjectIDFromHex(EntryID)
var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second)
var entry bson.M
if err := entryCollection.FindOne(ctx, bson.M{"_id": docID}).Decode(&entry); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
defer cancel()
fmt.Println(entry)
c.JSON(http.StatusOK, entry)
}
func UpdateIngredient(c *gin.Context) {
entryID := c.Params.ByName("id")
docID, _ := primitive.ObjectIDFromHex(entryID)
var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second)
type Ingredient struct {
Ingredients *string `json:"ingredients"`
}
var ingredient Ingredient
if err := c.BindJSON(&ingredient); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
result, err := entryCollection.UpdateOne(ctx, bson.M{"_id": docID},
bson.D{{"$set", bson.D{{"ingredients", ingredient.Ingredients}}}},
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
defer cancel()
c.JSON(http.StatusOK, result.ModifiedCount)
}
func UpdateEntry(c *gin.Context) {
entryID := c.Params.ByName("id")
docID, _ := primitive.ObjectIDFromHex(entryID)
var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second)
var entry models.Entry
if err := c.BindJSON(&entry); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
validationErr := validate.Struct(entry)
if validationErr != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": validationErr.Error()})
fmt.Println(validationErr)
return
}
result, err := entryCollection.ReplaceOne(
ctx,
bson.M{"_id": docID},
bson.M{
"dish": entry.Dish,
"fat": entry.Fat,
"ingredients": entry.Ingredients,
"calories": entry.Calories,
},
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
defer cancel()
c.JSON(http.StatusOK, result.ModifiedCount)
}
func DeleteEntry(c *gin.Context) {
entryID := c.Params.ByName("id")
docID, _ := primitive.ObjectIDFromHex(entryID)
var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second)
result, err := entryCollection.DeleteOne(ctx, bson.M{"_id": docID})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
fmt.Println(err)
return
}
defer cancel()
c.JSON(http.StatusOK, result.DeletedCount)
}
These functions handle the creation, retrieval, updating, and deletion of entries in the MongoDB collection.
Step 4: Main Application
The main
function sets up the routes and starts the server.
package main
import (
"os"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/your_username/your_project_name/routes"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8000"
}
router := gin.New()
router.Use(gin.Logger())
router.Use(cors.Default())
router.POST("/entry/create", routes.AddEntry)
router.GET("/entries", routes.GetEntries)
router.GET("/entry/:id/", routes.GetEntryById)
router.GET("/ingredient/:ingredient", routes.GetEntriesByIngredient)
router.PUT("/entry/update/:id", routes.UpdateEntry)
router.PUT("/ingredient/update/:id", routes.UpdateIngredient)
router.DELETE("/entry/delete/:id", routes.DeleteEntry)
router.Run(":" + port)
}
This sets up the endpoints:
POST /entry/create
to add a new entry.GET /entries
to get all entries.GET /entry/:id
to get a single
entry by ID.
GET /ingredient/:ingredient
to get entries by ingredient.PUT /entry/update/:id
to update an entry.PUT /ingredient/update/:id
to update the ingredients of an entry.DELETE /entry/delete/:id
to delete an entry.
With the backend set up, you can now start the server by running:
go run main.go
Your backend should now be running on http://localhost:8000
.
Next, let’s set up the React frontend.
Frontend: React with Axios and Bootstrap
Step 1: Set Up the Project
First, ensure you have Node.js and Create React App installed. Then, create a new React project and install the necessary dependencies.
npx create-react-app calorie-tracker-frontend
cd calorie-tracker-frontend
npm install axios bootstrap react-bootstrap
Step 2: Create the Components
We’ll create three main components: App
, Entries
, and Entry
.
App Component
This is the root component of the application.
import React from "react"; import "bootstrap/dist/css/bootstrap.css"; import Entries from "./components/entries.components"; function App() { return ( <div> <Entries /> </div> ); } export default App;
Entries Component
This component manages the list of entries and handles adding, updating, and deleting entries.
import React, { useState, useEffect } from "react"; import axios from "axios"; import { Button, Form, Container, Modal } from "react-bootstrap"; import Entry from "./single-entry.component"; const Entries = () => { const [entries, setEntries] = useState([]); const [refreshData, setRefreshData] = useState(false); const [changeEntry, setChangeEntry] = useState({ change: false, id: 0 }); const [changeIngredient, setChangeIngredient] = useState({ change: false, id: 0, }); const [newIngredientName, setNewIngredientName] = useState(""); const [addNewEntry, setAddNewEntry] = useState(false); const [newEntry, setNewEntry] = useState({ dish: "", ingredients: "", calories: 0, fat: 0, }); useEffect(() => { getAllEntries(); }, []); useEffect(() => { if (refreshData) { setRefreshData(false); getAllEntries(); } }, [refreshData]); return ( <div> <Container> <Button onClick={() => setAddNewEntry(true)}> Track today's calories </Button> </Container> <Container> {entries && entries.map((entry, i) => ( <Entry key={i} entryData={entry} deleteSingleEntry={deleteSingleEntry} setChangeIngredient={setChangeIngredient} setChangeEntry={setChangeEntry} /> ))} </Container> <Modal show={addNewEntry} onHide={() => setAddNewEntry(false)} centered > <Modal.Header closeButton> <Modal.Title>Add Calorie Entry</Modal.Title> </Modal.Header> <Modal.Body> <Form.Group> <Form.Label>Dish</Form.Label> <Form.Control onChange={(event) => { newEntry.dish = event.target.value; }} /> <Form.Label>Ingredients</Form.Label> <Form.Control onChange={(event) => { newEntry.ingredients = event.target.value; }} /> <Form.Label>Calories</Form.Label> <Form.Control onChange={(event) => { newEntry.calories = event.target.value; }} /> <Form.Label>Fat</Form.Label> <Form.Control type="number" onChange={(event) => { newEntry.fat = event.target.value; }} /> </Form.Group> <Button onClick={() => addSingleEntry()}>Add</Button> <Button onClick={() => setAddNewEntry(false)}>Cancel</Button> </Modal.Body> </Modal> <Modal show={changeIngredient.change} onHide={() => setChangeIngredient({ change: false, id: 0 })} centered > <Modal.Header closeButton> <Modal.Title>Change Ingredients</Modal.Title> </Modal.Header> <Modal.Body> <Form.Group> <Form.Label>New Ingredients</Form.Label> <Form.Control onChange={(event) => { setNewIngredientName(event.target.value); }} /> </Form.Group> <Button onClick={() => changeIngredientForEntry()}>Change</Button> <Button onClick={() => setChangeIngredient({ change: false, id: 0 })} > Cancel </Button> </Modal.Body> </Modal> <Modal show={changeEntry.change} onHide={() => setChangeEntry({ change: false, id: 0 })} centered > <Modal.Header closeButton> <Modal.Title>Change Entry</Modal.Title> </Modal.Header> <Modal.Body> <Form.Group> <Form.Label>Dish</Form.Label> <Form.Control onChange={(event) => { newEntry.dish = event.target.value; }} /> <Form.Label>Ingredients</Form.Label> <Form.Control onChange={(event) => { newEntry.ingredients = event.target.value; }} /> <Form.Label>Calories</Form.Label> <Form.Control onChange={(event) => { newEntry.calories = event.target.value; }} /> <Form.Label>Fat</Form.Label> <Form.Control type="number" onChange={(event) => { newEntry.fat = event.target.value; }} /> </Form.Group> <Button onClick={() => changeSingleEntry()}>Change</Button> <Button onClick={() => setChangeEntry({ change: false, id: 0 })}> Cancel </Button> </Modal.Body> </Modal> </div> ); function changeIngredientForEntry() { const url = "http://localhost:8000/ingredient/update/" + changeIngredient.id; axios.put(url, { ingredients: newIngredientName }).then((response) => { if (response.status === 200) { setRefreshData(true); } }); setChangeIngredient({ change: false, id: 0 }); } function changeSingleEntry() { const url = "http://localhost:8000/entry/update/" + changeEntry.id; axios.put(url, newEntry).then((response) => { if (response.status === 200) { setRefreshData(true); } }); setChangeEntry({ change: false, id: 0 }); } function addSingleEntry() { const url = "http://localhost:8000/entry/create"; axios .post(url, { dish: newEntry.dish, ingredients: newEntry.ingredients, calories: newEntry.calories, fat: parseFloat(newEntry.fat), }) .then((response) => { if (response.status === 200) { setRefreshData(true); } }); setAddNewEntry(false); } function deleteSingleEntry(id) { const url = "http://localhost:8000/entry/delete/" + id; axios.delete(url).then((response) => { if (response.status === 200) { setRefreshData(true); } }); } function getAllEntries() { const url = "http://localhost:8000/entries"; axios.get(url, { responseType: "json" }).then((response) => { if (response.status === 200) { setEntries(response.data); } }); } }; export default Entries;
Entry Component
This component represents a single entry in the list.
import React from "react"; import "bootstrap/dist/css/bootstrap.css"; import { Button, Card, Row, Col } from "react-bootstrap"; const Entry = ({ entryData, setChangeIngredient, deleteSingleEntry, setChangeEntry, }) => { return ( <Card> <Row> <Col>Dish: {entryData.dish}</Col> <Col>Ingredients: {entryData.ingredients}</Col> <Col>Calories: {entryData.calories}</Col> <Col>Fat: {entryData.fat}</Col> <Col> <Button onClick={() => deleteSingleEntry(entryData._id)}> Delete Entry </Button> </Col> <Col> <Button onClick={() => changeIngredient()}> Change Ingredients </Button> </Col> <Col> <Button onClick={() => changeEntry()}>Change Entry</Button> </Col> </Row> </Card> ); function changeIngredient() { setChangeIngredient({ change: true, id: entryData._id }); } function changeEntry() { setChangeEntry({ change: true, id: entryData._id }); } }; export default Entry;
Running the Application
Backend:
Ensure MongoDB is running, and then start the Go backend server:
go run main.go
Frontend:
Start the React development server:
npm start
Your React application should now be running on http://localhost:3000
, and it will interact with the backend running on http://localhost:8000
.
This setup provides a fully functional calorie tracker application with a Go backend and a React frontend. The backend handles CRUD operations for calorie entries stored in MongoDB, and the frontend allows users to interact with these entries through a web interface.
Learn How To Build AI Projects
Now, if you are interested in upskilling in 2024 with AI development, check out this 6 AI advanced projects with Golang where you will learn about building with AI and getting the best knowledge there is currently. Here’s the link.
Last updated 17 Aug 2024, 12:31 +0200 .