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.

  1. 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;
      
  2. 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;
      
  3. 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

  1. Backend:

    Ensure MongoDB is running, and then start the Go backend server:

      go run main.go
      
  2. 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 . history