diff --git a/backend/secfit/meals/__init__.py b/backend/secfit/meals/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/secfit/meals/admin.py b/backend/secfit/meals/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d1abccee378ef12128588685532046e527cdfe7
--- /dev/null
+++ b/backend/secfit/meals/admin.py
@@ -0,0 +1,9 @@
+"""Module for registering models from meals app to admin page so that they appear
+"""
+from django.contrib import admin
+
+# Register your models here.
+from .models import Meal, MealFile
+
+admin.site.register(Meal)
+admin.site.register(MealFile)
diff --git a/backend/secfit/meals/apps.py b/backend/secfit/meals/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..2268b89608a2601063194bd91d487e9028d0cb8b
--- /dev/null
+++ b/backend/secfit/meals/apps.py
@@ -0,0 +1,13 @@
+"""AppConfig for meals app
+"""
+from django.apps import AppConfig
+
+
+class MealsConfig(AppConfig):
+    """AppConfig for meals app
+
+    Attributes:
+        name (str): The name of the application
+    """
+
+    name = "meals"
diff --git a/backend/secfit/meals/migrations/0001_initial.py b/backend/secfit/meals/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..2247d71aa5c80b513df4081df8c8ed90cc5442b9
--- /dev/null
+++ b/backend/secfit/meals/migrations/0001_initial.py
@@ -0,0 +1,42 @@
+# Generated by Django 3.1 on 2021-10-20 15:17
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import meals.models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Meal',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+                ('date', models.DateTimeField()),
+                ('notes', models.TextField()),
+                ('calories', models.IntegerField()),
+                ('is_veg', models.BooleanField(default=False)),
+                ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meals', to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'ordering': ['-date'],
+            },
+        ),
+        migrations.CreateModel(
+            name='MealFile',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('file', models.FileField(upload_to=meals.models.meal_directory_path)),
+                ('meal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to='meals.meal')),
+                ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meal_files', to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]
diff --git a/backend/secfit/meals/migrations/__init__.py b/backend/secfit/meals/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/secfit/meals/mixins.py b/backend/secfit/meals/mixins.py
new file mode 100644
index 0000000000000000000000000000000000000000..e175c8d2ec0adf74510f1a5eeaf7137033a53e47
--- /dev/null
+++ b/backend/secfit/meals/mixins.py
@@ -0,0 +1,26 @@
+"""
+Mixins for the meals application
+"""
+
+
+class CreateListModelMixin(object):
+    """Mixin that allows to create multiple objects from lists.
+    Taken from https://stackoverflow.com/a/48885641
+    """
+
+    def get_serializer(self, *args, **kwargs):
+        """If an array is passed, set serializer to many.
+
+        kwargs["many"] will be set to true if an array is passed. This argument
+        is passed when retrieving the serializer.
+
+        Args:
+            *args: Variable length argument list passed to the serializer.
+            **kwargs: Arbitrary keyword arguments passed to the serializer, including "many".
+
+        Returns:
+            [type]: [description]
+        """
+        if isinstance(kwargs.get("data", {}), list):
+            kwargs["many"] = True
+        return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
diff --git a/backend/secfit/meals/models.py b/backend/secfit/meals/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..26e6f0bffac0c0a7f1e58c54f6d653bad68acd8b
--- /dev/null
+++ b/backend/secfit/meals/models.py
@@ -0,0 +1,82 @@
+"""Contains the models for the meals Django application. Users
+log meals (Meal). The user can also upload files (MealFile) .
+"""
+import os
+from django.db import models
+from django.core.files.storage import FileSystemStorage
+from django.conf import settings
+from django.contrib.auth import get_user_model
+
+
+class OverwriteStorage(FileSystemStorage):
+    """Filesystem storage for overwriting files. Currently unused."""
+
+    def get_available_name(self, name, max_length=None):
+        """https://djangosnippets.org/snippets/976/
+        Returns a filename that's free on the target storage system, and
+        available for new content to be written to.
+
+        Args:
+            name (str): Name of the file
+            max_length (int, optional): Maximum length of a file name. Defaults to None.
+        """
+        if self.exists(name):
+            os.remove(os.path.join(settings.MEDIA_ROOT, name))
+
+
+# Create your models here.
+class Meal(models.Model):
+    """Django model for a meal that users can log.
+
+    A meal has several attributes, and files uploaded by the user.
+
+    Attributes:
+        name:        Name of the meal
+        date:        Date and time the meal was consumed
+        notes:       Notes about the meal
+        calories:    Total amount of calories in the meal
+        is_veg:      Whether the meal was vegetarian or not
+        owner:       User that logged the meal
+    """
+
+    name = models.CharField(max_length=100)
+    date = models.DateTimeField()
+    notes = models.TextField()
+    calories = models.IntegerField()
+    is_veg = models.BooleanField(default=False)
+    owner = models.ForeignKey(
+        get_user_model(), on_delete=models.CASCADE, related_name="meals"
+    )
+
+    class Meta:
+        ordering = ["-date"]
+
+    def __str__(self):
+        return self.name
+
+def meal_directory_path(instance, filename):
+    """Return path for which meal files should be uploaded on the web server
+
+    Args:
+        instance (MealFile): MealFile instance
+        filename (str): Name of the file
+
+    Returns:
+        str: Path where workout file is stored
+    """
+    return f"meals/{instance.meal.id}/{filename}"
+
+class MealFile(models.Model):
+    """Django model for file associated with a meal. Basically a wrapper.
+
+    Attributes:
+        meal:    The meal for which this file has been uploaded
+        owner:   The user who uploaded the file
+        file:    The actual file that's being uploaded
+    """
+
+    meal = models.ForeignKey(Meal, on_delete=models.CASCADE, related_name="files")
+    owner = models.ForeignKey(
+        get_user_model(), on_delete=models.CASCADE, related_name="meal_files"
+    )
+    file = models.FileField(upload_to=meal_directory_path)
diff --git a/backend/secfit/meals/parsers.py b/backend/secfit/meals/parsers.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf1646b4f0b35ec71190790dea3f8e5b3b7ea070
--- /dev/null
+++ b/backend/secfit/meals/parsers.py
@@ -0,0 +1,38 @@
+"""Contains custom parsers for serializers from the meals Django app
+"""
+import json
+from rest_framework import parsers
+
+# Thanks to https://stackoverflow.com/a/50514630
+class MultipartJsonParser(parsers.MultiPartParser):
+    """Parser for serializing multipart data containing both files and JSON.
+
+    This is currently unused.
+    """
+
+    def parse(self, stream, media_type=None, parser_context=None):
+        result = super().parse(
+            stream, media_type=media_type, parser_context=parser_context
+        )
+        data = {}
+        new_files = {"files": []}
+
+        # for case1 with nested serializers
+        # parse each field with json
+        for key, value in result.data.items():
+            if not isinstance(value, str):
+                data[key] = value
+                continue
+            if "{" in value or "[" in value:
+                try:
+                    data[key] = json.loads(value)
+                except ValueError:
+                    data[key] = value
+            else:
+                data[key] = value
+
+        files = result.files.getlist("files")
+        for file in files:
+            new_files["files"].append({"file": file})
+
+        return parsers.DataAndFiles(data, new_files)
diff --git a/backend/secfit/meals/permissions.py b/backend/secfit/meals/permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a43a52f77eae42d90ec09ca66df95be135a965d
--- /dev/null
+++ b/backend/secfit/meals/permissions.py
@@ -0,0 +1,35 @@
+"""Contains custom DRF permissions classes for the meals app
+"""
+from rest_framework import permissions
+from meals.models import Meal
+
+
+class IsOwner(permissions.BasePermission):
+    """Checks whether the requesting user is also the owner of the existing object"""
+
+    def has_object_permission(self, request, view, obj):
+        return obj.owner == request.user
+
+
+class IsOwnerOfMeal(permissions.BasePermission):
+    """Checks whether the requesting user is also the owner of the new or existing object"""
+
+    def has_permission(self, request, view):
+        if request.method == "POST":
+            if request.data.get("meal"):
+                meal_id = request.data["meal"].split("/")[-2]
+                meal = Meal.objects.get(pk=meal_id)
+                if meal:
+                    return meal.owner == request.user
+            return False
+
+        return True
+
+    def has_object_permission(self, request, view, obj):
+        return obj.meal.owner == request.user
+
+class IsReadOnly(permissions.BasePermission):
+    """Checks whether the HTTP request verb is only for retrieving data (GET, HEAD, OPTIONS)"""
+
+    def has_object_permission(self, request, view, obj):
+        return request.method in permissions.SAFE_METHODS
diff --git a/backend/secfit/meals/serializers.py b/backend/secfit/meals/serializers.py
new file mode 100644
index 0000000000000000000000000000000000000000..adc327ab29eed62df00ff50050e455839dd8448b
--- /dev/null
+++ b/backend/secfit/meals/serializers.py
@@ -0,0 +1,139 @@
+"""Serializers for the meals application
+"""
+from rest_framework import serializers
+from rest_framework.serializers import HyperlinkedRelatedField
+from meals.models import Meal, MealFile
+
+class MealFileSerializer(serializers.HyperlinkedModelSerializer):
+    """Serializer for a MealFile. Hyperlinks are used for relationships by default.
+
+    Serialized fields: url, id, owner, file, meal
+
+    Attributes:
+        owner:      The owner (User) of the MealFile, represented by a username. ReadOnly
+        meal:       The associate meal for this MealFile, represented by a hyperlink
+    """
+
+    owner = serializers.ReadOnlyField(source="owner.username")
+    meal = HyperlinkedRelatedField(
+        queryset=Meal.objects.all(), view_name="meal-detail", required=False
+    )
+
+    class Meta:
+        model = MealFile
+        fields = ["url", "id", "owner", "file", "meal"]
+
+    def create(self, validated_data):
+        return MealFile.objects.create(**validated_data)
+
+
+class MealSerializer(serializers.HyperlinkedModelSerializer):
+    """Serializer for a Meal. Hyperlinks are used for relationships by default.
+
+    This serializer specifies nested serialization since a meal consists of MealFiles.
+
+    Serialized fields: url, id, name, date, notes, calories, owner, is_veg, owner_username, files
+
+    Attributes:
+        owner_username:     Username of the owning User
+        files:              Serializer for MealFiles
+    """
+
+    owner_username = serializers.SerializerMethodField()
+    files = MealFileSerializer(many=True, required=False)
+
+    class Meta:
+        model = Meal
+        fields = [
+            "url",
+            "id",
+            "name",
+            "date",
+            "notes",
+            "calories",
+            "is_veg",
+            "owner",
+            "owner_username",
+            "files",
+        ]
+        extra_kwargs = {"owner": {"read_only": True}}
+
+    def create(self, validated_data):
+        """Custom logic for creating MealFiles, and a Meal.
+
+        This is needed to iterate over the files, since this serializer is nested.
+
+        Args:
+            validated_data: Validated files
+
+        Returns:
+            Meal: A newly created Meal
+        """
+        files_data = []
+        if "files" in validated_data:
+            files_data = validated_data.pop("files")
+
+        meal = Meal.objects.create(**validated_data)
+
+        for file_data in files_data:
+            MealFile.objects.create(
+                meal=meal, owner=meal.owner, file=file_data.get("file")
+            )
+
+        return meal
+
+    def update(self, instance, validated_data):
+        """Custom logic for updating a Meal.
+
+        This is needed because each object in files must be iterated
+        over and handled individually.
+
+        Args:
+            instance (Meal): Current Meal object
+            validated_data: Contains data for validated fields
+
+        Returns:
+            Meal: Updated Meal instance
+        """
+
+        instance.name = validated_data.get("name", instance.name)
+        instance.date = validated_data.get("date", instance.date)
+        instance.notes = validated_data.get("notes", instance.notes)
+        instance.is_veg = validated_data.get("is_veg", instance.is_veg)
+        instance.calories = validated_data.get("calories", instance.calories)
+        instance.save()
+
+        # Handle MealFiles
+
+        if "files" in validated_data:
+            files_data = validated_data.pop("files")
+            files = instance.files
+
+            for file, file_data in zip(files.all(), files_data):
+                file.file = file_data.get("file", file.file)
+
+            # If new files have been added, creating new MealFiles
+            if len(files_data) > len(files.all()):
+                for i in range(len(files.all()), len(files_data)):
+                    MealFile.objects.create(
+                        meal=instance,
+                        owner=instance.owner,
+                        file=files_data[i].get("file"),
+                    )
+            # Else if files have been removed, delete MealFiles
+            elif len(files_data) < len(files.all()):
+                for i in range(len(files_data), len(files.all())):
+                    files.all()[i].delete()
+
+        return instance
+
+    def get_owner_username(self, obj):
+        """Returns the owning user's username
+
+        Args:
+            obj (Meal): Current Meal
+
+        Returns:
+            str: Username of owner
+        """
+        return obj.owner.username
\ No newline at end of file
diff --git a/backend/secfit/meals/tests.py b/backend/secfit/meals/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..241c0b65cf34c0dac389ad383b3c7b2145a55a83
--- /dev/null
+++ b/backend/secfit/meals/tests.py
@@ -0,0 +1,6 @@
+"""
+Tests for the meals application.
+"""
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/backend/secfit/meals/urls.py b/backend/secfit/meals/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..a880a65e51db27319c741737498182e67d5b2438
--- /dev/null
+++ b/backend/secfit/meals/urls.py
@@ -0,0 +1,31 @@
+from django.urls import path, include
+from meals import views
+from rest_framework.urlpatterns import format_suffix_patterns
+from rest_framework_simplejwt.views import (
+    TokenObtainPairView,
+    TokenRefreshView,
+)
+
+# This is messy and should be refactored
+urlpatterns = format_suffix_patterns(
+    [
+        path("", views.api_root),
+        path("api/meals/", views.MealList.as_view(), name="meal-list"),
+        path(
+            "api/meals/<int:pk>/",
+            views.MealDetail.as_view(),
+            name="meal-detail",
+        ),
+        path(
+            "api/meal-files/",
+            views.MealFileList.as_view(),
+            name="meal-file-list",
+        ),
+        path(
+            "api/meal-files/<int:pk>/",
+            views.MealFileDetail.as_view(),
+            name="mealfile-detail",
+        ),
+        path("", include("users.urls")),
+    ]
+)
diff --git a/backend/secfit/meals/views.py b/backend/secfit/meals/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..7eddefa9c92d026544a3979a4ea2d740f619bea3
--- /dev/null
+++ b/backend/secfit/meals/views.py
@@ -0,0 +1,155 @@
+"""Contains views for the meals application. These are mostly class-based views.
+"""
+from rest_framework import generics, mixins
+from rest_framework import permissions
+
+from rest_framework.parsers import (
+    JSONParser,
+)
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from django.db.models import Q
+from rest_framework import filters
+from meals.parsers import MultipartJsonParser
+from meals.permissions import (
+    IsOwner,
+    IsOwnerOfMeal,
+    IsReadOnly,
+)
+from meals.mixins import CreateListModelMixin
+from meals.models import Meal, MealFile
+from meals.serializers import MealSerializer
+from meals.serializers import MealFileSerializer
+from django.core.exceptions import PermissionDenied
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework.response import Response
+import json
+from collections import namedtuple
+import base64, pickle
+from django.core.signing import Signer
+
+
+@api_view(["GET"])
+def api_root(request, format=None):
+    return Response(
+        {
+            "users": reverse("user-list", request=request, format=format),
+            "meals": reverse("meal-list", request=request, format=format),
+            "meal-files": reverse(
+                "meal-file-list", request=request, format=format
+            ),
+        }
+    )
+
+class MealList(
+    mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
+):
+    """Class defining the web response for the creation of a Meal, or displaying a list
+    of Meals
+
+    HTTP methods: GET, POST
+    """
+
+    serializer_class = MealSerializer
+    permission_classes = [
+        permissions.IsAuthenticated
+    ]  # User must be authenticated to create/view meals
+    parser_classes = [
+        MultipartJsonParser,
+        JSONParser,
+    ]  # For parsing JSON and Multi-part requests
+    filter_backends = [filters.OrderingFilter]
+    ordering_fields = ["name", "date", "owner__username"]
+
+    def get(self, request, *args, **kwargs):
+        return self.list(request, *args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        return self.create(request, *args, **kwargs)
+
+    def perform_create(self, serializer):
+        serializer.save(owner=self.request.user)
+
+    def get_queryset(self):
+        qs = Meal.objects.none()
+        if self.request.user:
+            qs = Meal.objects.filter(Q(owner=self.request.user)).distinct()
+        return qs
+
+class MealDetail(
+    mixins.RetrieveModelMixin,
+    mixins.UpdateModelMixin,
+    mixins.DestroyModelMixin,
+    generics.GenericAPIView,
+):
+    """Class defining the web response for the details of an individual Meal.
+
+    HTTP methods: GET, PUT, DELETE
+    """
+
+    queryset = Meal.objects.all()
+    serializer_class = MealSerializer
+    permission_classes = [
+        permissions.IsAuthenticated
+        & IsOwner
+    ]
+    parser_classes = [MultipartJsonParser, JSONParser]
+
+    def get(self, request, *args, **kwargs):
+        return self.retrieve(request, *args, **kwargs)
+
+    def put(self, request, *args, **kwargs):
+        return self.update(request, *args, **kwargs)
+
+    def delete(self, request, *args, **kwargs):
+        return self.destroy(request, *args, **kwargs)
+
+class MealFileList(
+    mixins.ListModelMixin,
+    mixins.CreateModelMixin,
+    CreateListModelMixin,
+    generics.GenericAPIView,
+):
+
+    queryset = MealFile.objects.all()
+    serializer_class = MealFileSerializer
+    permission_classes = [permissions.IsAuthenticated & IsOwnerOfMeal]
+    parser_classes = [MultipartJsonParser, JSONParser]
+
+    def get(self, request, *args, **kwargs):
+        return self.list(request, *args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        return self.create(request, *args, **kwargs)
+
+    def perform_create(self, serializer):
+        serializer.save(owner=self.request.user)
+
+    def get_queryset(self):
+        qs = MealFile.objects.none()
+        if self.request.user:
+            qs = MealFile.objects.filter(
+                Q(owner=self.request.user)).distinct()
+        return qs
+
+
+class MealFileDetail(
+    mixins.RetrieveModelMixin,
+    mixins.UpdateModelMixin,
+    mixins.DestroyModelMixin,
+    generics.GenericAPIView,
+):
+
+    queryset = MealFile.objects.all()
+    serializer_class = MealFileSerializer
+    permission_classes = [
+        permissions.IsAuthenticated
+        & IsOwner 
+    ]
+
+    def get(self, request, *args, **kwargs):
+        return self.retrieve(request, *args, **kwargs)
+
+    def delete(self, request, *args, **kwargs):
+        return self.destroy(request, *args, **kwargs)
diff --git a/backend/secfit/secfit/settings.py b/backend/secfit/secfit/settings.py
index dcf82bd7fe57a4725c448b93848d998b9ff2227c..96dbdcde7b3df80ade7cb75f08ebb31353b918c7 100644
--- a/backend/secfit/secfit/settings.py
+++ b/backend/secfit/secfit/settings.py
@@ -59,6 +59,7 @@ INSTALLED_APPS = [
     "django.contrib.staticfiles",
     "rest_framework",
     "workouts.apps.WorkoutsConfig",
+    "meals.apps.MealsConfig",
     "users.apps.UsersConfig",
     "comments.apps.CommentsConfig",
     "corsheaders",
diff --git a/backend/secfit/secfit/urls.py b/backend/secfit/secfit/urls.py
index 3146886ed88c67c7f8838c74cea38c7b7ee5555a..93fc1902dfcf8c10bce7f680b763d92756d6eae9 100644
--- a/backend/secfit/secfit/urls.py
+++ b/backend/secfit/secfit/urls.py
@@ -21,6 +21,7 @@ from django.conf.urls.static import static
 urlpatterns = [
     path("admin/", admin.site.urls),
     path("", include("workouts.urls")),
+    path("", include("meals.urls")),
 ]
 
 urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
diff --git a/backend/secfit/workouts/migrations/0004_auto_20211020_0950.py b/backend/secfit/workouts/migrations/0004_auto_20211020_0950.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d83c6037c048480abd0f1c9d8f522101b7e9061
--- /dev/null
+++ b/backend/secfit/workouts/migrations/0004_auto_20211020_0950.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.1 on 2021-10-20 07:50
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('workouts', '0003_rememberme'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='exercise',
+            name='calories',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AddField(
+            model_name='exercise',
+            name='duration',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AddField(
+            model_name='exercise',
+            name='muscleGroup',
+            field=models.TextField(default='Legs'),
+        ),
+    ]
diff --git a/frontend/www/meal.html b/frontend/www/meal.html
new file mode 100644
index 0000000000000000000000000000000000000000..2337e424143743747b439b9a9101a348a0927fd1
--- /dev/null
+++ b/frontend/www/meal.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Meal</title>
+
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
+
+    <script src="https://kit.fontawesome.com/0ce6c392ca.js" crossorigin="anonymous"></script>
+    <link rel="stylesheet" href="styles/style.css">
+    <script src="scripts/navbar.js" type="text/javascript" defer></script>
+</head>
+<body>
+  <navbar-el></navbar-el>
+
+    <div class="container">
+        <div class="row">
+            <div class="col-lg">
+                <h3 class="mt-3">View or edit your registered meal</h3>
+            </div>
+        </div>
+      <form class="row g-3 mb-4" id="form-meal">
+          <div class="col-lg-6 ">
+            <label for="inputName" class="form-label">Name</label>
+            <input type="text" class="form-control" id="inputName" name="name" readonly>
+          </div>
+          <div class="col-lg-6"></div>
+          <div class="col-lg-6">
+            <label for="inputDateTime" class="form-label">Date/Time</label>
+            <input type="datetime-local" class="form-control" id="inputDateTime" name="date" readonly>
+          </div>
+          <div class="col-lg-6"></div>
+          <div class="col-lg-6">
+              <label for="inputOwner" class="form-label">Owner</label>
+              <input type="text" class="form-control" id="inputOwner" name="owner_username" readonly>
+          </div>
+          <div class="col-lg-6"></div>
+          <div class="col-lg-6">
+            <label for="inputNotes" class="form-label">Notes</label>
+            <textarea class="form-control" id="inputNotes" name="notes" readonly></textarea>
+          </div>
+          <div class="col-lg-6"></div>
+          <div class="col-lg-6">
+            <label for="inputCalories" class="form-label">Calories</label>
+            <input type="number" class="form-control" id="inputCalories" name="calories" readonly></input>
+          </div>
+          <div class="col-lg-6"></div>
+          <div class="col-lg-6">
+            <div class="input-group">
+                <input type="file" class="form-control" id="customFile" name="files" multiple disabled>
+            </div>
+            <div id="uploaded-files" class="ms-1 mt-2">            
+            </div>
+          </div>
+          <div class="col-lg-6">
+          </div>
+          <div class="col-lg-6">
+            <input type="button" class="btn btn-primary hide" id="btn-ok-meal" value="  OK  ">
+            <input type="button" class="btn btn-primary hide" id="btn-edit-meal" value=" Edit ">
+            <input type="button" class="btn btn-secondary hide" id="btn-cancel-meal" value="Cancel">
+            <input type="button" class="btn btn-danger float-end hide" id="btn-delete-meal" value="Delete">
+          </div>
+          <div class="col-lg-6"></div>
+        </form>
+    </div> 
+    
+    <script src="scripts/defaults.js"></script>
+    <script src="scripts/scripts.js"></script>
+    <script src="scripts/meal.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/frontend/www/meals.html b/frontend/www/meals.html
new file mode 100644
index 0000000000000000000000000000000000000000..424969be5d187d00995f57c1b364b7f396394704
--- /dev/null
+++ b/frontend/www/meals.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Meals</title>
+    <link rel="stylesheet" href="styles/style.css">
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
+
+    <script src="https://kit.fontawesome.com/0ce6c392ca.js" crossorigin="anonymous"></script>
+    <script src="scripts/navbar.js" type="text/javascript" defer></script>
+</head>
+<body>
+    <navbar-el></navbar-el>
+
+    <div class="container">
+        <div class="row">
+            <div class="col-lg text-center">
+                <h3 class="mt-5">View meals</h3>
+                <p>Here you can view your logged meals. Click on a meal to view its details.</p>
+                <input type="button" class="btn btn-success" id="btn-create-meal" value="Log new meal">
+            </div>
+        </div>
+        <div class="row">
+            <div class="col-lg text-center">
+                <div class="mt-1">Sort by: <a href="?ordering=date">Date</a> <a href="?ordering=name">Name</a>
+                    <br>Currently sorting by: <span id="current-sort"></span>
+                </div>
+                <div class="list-group mt-1" id="div-content"></div>
+              </div>
+        </div>
+
+    </div>
+
+    <template id="template-meal">
+        <a class="list-group-item list-group-item-action flex-column align-items-start my-1 meal">
+            <div class="d-flex w-100 justify-content-between align-items-center">
+                <h5 class="mb-1"></h5>
+            </div>
+            <div class="d-flex">
+                <table class="mb-1 text-start">
+                    <tr><td>Date:</td><td></td></tr>
+                    <tr><td>Time:</td><td></td></tr>
+                    <tr><td>Owner:</td><td></td></tr>
+                </table>       
+            </div>
+        </a>
+    </template>
+
+    <script src="scripts/defaults.js"></script>
+    <script src="scripts/scripts.js"></script>
+    <script src="scripts/meals.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/frontend/www/scripts/exercise.js b/frontend/www/scripts/exercise.js
index b2f74385abb8e6a4805c8055d818506d863f37fb..f845fe1844b633cf1b0bf1365eee4323c4c84bcc 100644
--- a/frontend/www/scripts/exercise.js
+++ b/frontend/www/scripts/exercise.js
@@ -6,14 +6,29 @@ let oldFormData;
 
 class MuscleGroup { 
     constructor(type) {
-        this.type = type;
+        this.isValidType = false;
+        this.validTypes = ["Legs", "Chest", "Back", "Arms", "Abdomen", "Shoulders"]
+
+        this.type = this.validTypes.includes(type) ? type : undefined;
     };
 
     setMuscleGroupType = (newType) => {
-        this.type = newType
+        this.isValidType = false;
+        
+        if(this.validTypes.includes(newType)){
+            this.isValidType = true;
+            this.type = newType;
+        }
+        else{
+            alert("Invalid muscle group!");
+        }
+
     };
     
-    getMuscleGroupType = () => this.type;
+    getMuscleGroupType = () => {
+        console.log(this.type, "SWIOEFIWEUFH")
+        return this.type;
+    }
 }
 
 function handleCancelButtonDuringEdit() {
diff --git a/frontend/www/scripts/meal.js b/frontend/www/scripts/meal.js
new file mode 100644
index 0000000000000000000000000000000000000000..dd48cc90640ad36ac02f96885b10b0727d05070a
--- /dev/null
+++ b/frontend/www/scripts/meal.js
@@ -0,0 +1,158 @@
+let cancelMealButton;
+let okMealButton;
+let deleteMealButton;
+let editMealButton;
+
+async function retrieveMeal(id) {  
+    let mealData = null;
+    let response = await sendRequest("GET", `${HOST}/api/meals/${id}/`);
+    if (!response.ok) {
+        let data = await response.json();
+        let alert = createAlert("Could not retrieve your meal data!", data);
+        document.body.prepend(alert);
+    } else {
+        mealData = await response.json();
+        let form = document.querySelector("#form-meal");
+        let formData = new FormData(form);
+
+        for (let key of formData.keys()) {
+            let selector = `input[name="${key}"], textarea[name="${key}"]`;
+            let input = form.querySelector(selector);
+            let newVal = mealData[key];
+            if (key == "date") {
+                // Creating a valid datetime-local string with the correct local time
+                let date = new Date(newVal);
+                date = new Date(date.getTime() - (date.getTimezoneOffset() * 60 * 1000)).toISOString(); // get ISO format for local time
+                newVal = date.substring(0, newVal.length - 1);    // remove Z (since this is a local time, not UTC)
+            }
+            if (key != "files") {
+                input.value = newVal;
+            }
+        }
+
+        let input = form.querySelector("select:disabled");
+        // files
+        let filesDiv = document.querySelector("#uploaded-files");
+        for (let file of mealData.files) {
+            let a = document.createElement("a");
+            a.href = file.file;
+            let pathArray = file.file.split("/");
+            a.text = pathArray[pathArray.length - 1];
+            a.className = "me-2";
+            filesDiv.appendChild(a);
+        }
+    }
+    return mealData;     
+}
+
+function handleCancelDuringMealEdit() {
+    location.reload();
+}
+
+function handleEditMealButtonClick() {
+    
+    setReadOnly(false, "#form-meal");
+    document.querySelector("#inputOwner").readOnly = true;  // owner field should still be readonly 
+
+    editMealButton.className += " hide"; // The edit button should be hidden when in edit mode
+    okMealButton.className = okMealButton.className.replace(" hide", ""); // The ok button should not be hidden when in edit mode
+    cancelMealButton.className = cancelMealButton.className.replace(" hide", ""); // See above
+    deleteMealButton.className = deleteMealButton.className.replace(" hide", ""); // See above
+    cancelMealButton.addEventListener("click", handleCancelDuringMealEdit);
+
+}
+
+async function deleteMeal(id) {
+    let response = await sendRequest("DELETE", `${HOST}/api/meals/${id}/`);
+    if (!response.ok) {
+        let data = await response.json();
+        let alert = createAlert(`Could not delete this meal. ID: ${id}!`, data);
+        document.body.prepend(alert);
+    } else {
+        window.location.replace("meals.html");
+    }
+}
+
+async function updateMeal(id) {
+    let submitForm = generateMealForm();
+
+    let response = await sendRequest("PUT", `${HOST}/api/meals/${id}/`, submitForm, "");
+    if (!response.ok) {
+        let data = await response.json();
+        let alert = createAlert("Could not update your meal! :-( ", data);
+        document.body.prepend(alert);
+    } else {
+        location.reload();
+    }
+}
+
+function generateMealForm() {
+    let form = document.querySelector("#form-meal");
+
+    let formData = new FormData(form);
+    let submitForm = new FormData();
+
+    submitForm.append("name", formData.get('name'));
+    let date = new Date(formData.get('date')).toISOString();
+    submitForm.append("date", date);
+    submitForm.append("notes", formData.get("notes"));
+    submitForm.append("calories", formData.get("calories"));
+
+    // Adds the files
+    for (let file of formData.getAll("files")) {
+        submitForm.append("files", file);
+    }
+    return submitForm;
+}
+
+async function createMeal() {
+    let submitForm = generateMealForm();
+
+    let response = await sendRequest("POST", `${HOST}/api/meals/`, submitForm, "");
+
+    if (response.ok) {
+        window.location.replace("meals.html");
+    } else {
+        let data = await response.json();
+        let alert = createAlert("Could not create new meal", data);
+        document.body.prepend(alert);
+    }
+}
+
+function handleCancelDuringMealCreate() {
+    window.location.replace("meals.html");
+}
+
+window.addEventListener("DOMContentLoaded", async () => {
+    cancelMealButton = document.querySelector("#btn-cancel-meal");
+    okMealButton = document.querySelector("#btn-ok-meal");
+    deleteMealButton = document.querySelector("#btn-delete-meal");
+    editMealButton = document.querySelector("#btn-edit-meal");
+
+    const urlParams = new URLSearchParams(window.location.search);
+    let currentUser = await getCurrentUser();
+
+    if (urlParams.has('id')) {
+        const id = urlParams.get('id');
+        let mealData = await retrieveMeal(id);
+
+        if (mealData["owner"] == currentUser.url) {
+            editMealButton.classList.remove("hide");
+            editMealButton.addEventListener("click", handleEditMealButtonClick);
+            deleteMealButton.addEventListener("click", (async (id) => await deleteMeal(id)).bind(undefined, id));
+            okMealButton.addEventListener("click", (async (id) => await updateMeal(id)).bind(undefined, id));
+        }
+    } else {
+        let ownerInput = document.querySelector("#inputOwner");
+        ownerInput.value = currentUser.username;
+        setReadOnly(false, "#form-meal");
+        ownerInput.readOnly = !ownerInput.readOnly;
+
+        okMealButton.className = okMealButton.className.replace(" hide", "");
+        cancelMealButton.className = cancelMealButton.className.replace(" hide", "");
+
+        okMealButton.addEventListener("click", async () => await createMeal());
+        cancelMealButton.addEventListener("click", handleCancelDuringMealCreate);
+    }
+
+});
\ No newline at end of file
diff --git a/frontend/www/scripts/meals.js b/frontend/www/scripts/meals.js
new file mode 100644
index 0000000000000000000000000000000000000000..2ffed1d12e1f6c0867d02b55c9862b4cd842072b
--- /dev/null
+++ b/frontend/www/scripts/meals.js
@@ -0,0 +1,91 @@
+async function fetchMeals(ordering) {
+    let response = await sendRequest("GET", `${HOST}/api/meals/?ordering=${ordering}`);
+
+    if (!response.ok) {
+        throw new Error(`HTTP error! status: ${response.status}`);
+    } else {
+        let data = await response.json();
+
+        let meals = data.results;
+        let container = document.getElementById('div-content');
+        meals.forEach(meal => {
+            let templateMeal = document.querySelector("#template-meal");
+            let cloneMeal = templateMeal.content.cloneNode(true);
+
+            let aMeal = cloneMeal.querySelector("a");
+            aMeal.href = `meal.html?id=${meal.id}`;
+
+            let h5 = aMeal.querySelector("h5");
+            h5.textContent = meal.name;
+
+            let localDate = new Date(meal.date);
+
+            let table = aMeal.querySelector("table");
+            let rows = table.querySelectorAll("tr");
+            rows[0].querySelectorAll("td")[1].textContent = localDate.toLocaleDateString(); // Date
+            rows[1].querySelectorAll("td")[1].textContent = localDate.toLocaleTimeString(); // Time
+            rows[2].querySelectorAll("td")[1].textContent = meal.owner_username; //Owner
+
+            container.appendChild(aMeal);
+        });
+        return meals;
+    }
+}
+
+function createMeal() {
+    window.location.replace("meal.html");
+}
+
+window.addEventListener("DOMContentLoaded", async () => {
+    let createButton = document.querySelector("#btn-create-meal");
+    createButton.addEventListener("click", createMeal);
+    let ordering = "-date";
+
+    const urlParams = new URLSearchParams(window.location.search);
+    if (urlParams.has('ordering')) {
+        let aSort = null;
+        ordering = urlParams.get('ordering');
+        if (ordering == "name" || ordering == "owner" || ordering == "date") {
+                let aSort = document.querySelector(`a[href="?ordering=${ordering}"`);
+                aSort.href = `?ordering=-${ordering}`;
+        } 
+    } 
+
+    let currentSort = document.querySelector("#current-sort");
+    currentSort.innerHTML = (ordering.startsWith("-") ? "Descending" : "Ascending") + " " + ordering.replace("-", "");
+
+    let currentUser = await getCurrentUser();
+    // grab username
+    if (ordering.includes("owner")) {
+        ordering += "__username";
+    }
+    let meals = await fetchMeals(ordering);
+    
+    let tabEls = document.querySelectorAll('a[data-bs-toggle="list"]');
+    for (let i = 0; i < tabEls.length; i++) {
+        let tabEl = tabEls[i];
+        tabEl.addEventListener('show.bs.tab', function (event) {
+            let mealAnchors = document.querySelectorAll('.meal');
+            for (let j = 0; j < meals.length; j++) {
+                // I'm assuming that the order of meal objects matches
+                // the other of the meal anchor elements. They should, given
+                // that I just created them.
+                let meal = meals[j];
+                let mealAnchor = mealAnchors[j];
+
+                switch (event.currentTarget.id) {
+                    case "list-my-meals-list":
+                        if (meal.owner == currentUser.url) {
+                            mealAnchor.classList.remove('hide');
+                        } else {
+                            mealAnchor.classList.add('hide');
+                        }
+                        break;
+                    default :
+                        mealAnchor.classList.remove('hide');
+                        break;
+                }
+            }
+        });
+    }
+});
\ No newline at end of file
diff --git a/frontend/www/scripts/navbar.js b/frontend/www/scripts/navbar.js
index d906f613e77b06fe961e37151d1eea6f5668b48a..8dec8c1a20089ea9bbb85e5df99001473ec64c2c 100644
--- a/frontend/www/scripts/navbar.js
+++ b/frontend/www/scripts/navbar.js
@@ -18,6 +18,7 @@ class NavBar extends HTMLElement {
                 <a class="nav-link hide" id="nav-exercises" href="exercises.html">Exercises</a>
                 <a class="nav-link hide" id="nav-mycoach" href="mycoach.html">Coach</a>
                 <a class="nav-link hide" id="nav-myathletes" href="myathletes.html">Athletes</a>
+                <a class="nav-link hide" id="nav-meals" href="meals.html">Meal registration</a>
                 <hr>
             </div>
             <div class="my-2 my-lg-0 me-5">
diff --git a/frontend/www/scripts/scripts.js b/frontend/www/scripts/scripts.js
index 8c550009c43a70acc44ecd56c4434faa318c1c69..9bfc1efb688400bcabfa9457b05be97f8dbd9c92 100644
--- a/frontend/www/scripts/scripts.js
+++ b/frontend/www/scripts/scripts.js
@@ -22,6 +22,8 @@ function updateNavBar() {
     makeNavLinkActive("nav-mycoach")
   } else if (window.location.pathname == "/myathletes.html") {
     makeNavLinkActive("nav-myathletes");
+  } else if (window.location.pathname == "/meals.html") {
+    makeNavLinkActive("nav-myathletes");
   }
 
   if (isUserAuthenticated()) {
@@ -32,6 +34,7 @@ function updateNavBar() {
     document.querySelector('a[href="mycoach.html"').classList.remove("hide");
     document.querySelector('a[href="exercises.html"').classList.remove("hide");
     document.querySelector('a[href="myathletes.html"').classList.remove("hide");
+    document.querySelector('a[href="meals.html"').classList.remove("hide");
   } else {
     document.getElementById("btn-login-nav").classList.remove("hide");
     document.getElementById("btn-register").classList.remove("hide");