diff --git a/backend/api_test.go b/backend/api_test.go
index e3de9f0..48a35c6 100644
--- a/backend/api_test.go
+++ b/backend/api_test.go
@@ -2,8 +2,9 @@ package main
 
 import (
 	"testing"
+
+	"github.com/gavv/httpexpect/v2"
 )
-import "github.com/gavv/httpexpect/v2"
 
 func startServer(t *testing.T) *httpexpect.Expect {
 	config := ServerConfig{
@@ -56,3 +57,95 @@ func TestGetUserGoalsBadId(t *testing.T) {
 	e := startServer(t)
 	e.GET("/user/bad-id/goals").Expect().Status(400)
 }
+
+func TestCreateUserGoal(t *testing.T) {
+	e := startServer(t)
+
+	// Create a new goal
+	requestBody := map[string]interface{}{
+		"name":   "Test Goal",
+		"target": 5000,
+		"weight": 10,
+	}
+
+	response := e.POST("/user/1/goals").
+		WithJSON(requestBody).
+		Expect().
+		Status(201).
+		JSON().Object()
+
+	// Verify the response has an ID
+	response.ContainsKey("id")
+	goalId := response.Value("id").Number().Raw()
+
+	// Verify the goal exists in the list of goals
+	goals := e.GET("/user/1/goals").
+		Expect().
+		Status(200).
+		JSON().Array()
+
+	goals.Length().IsEqual(1)
+
+	goal := goals.Value(0).Object()
+	goal.Value("id").IsEqual(goalId)
+	goal.Value("name").IsEqual("Test Goal")
+	goal.Value("target").IsEqual(5000)
+	goal.Value("weight").IsEqual(10)
+	goal.Value("progress").IsEqual(0)
+}
+
+func TestCreateUserGoalNoUser(t *testing.T) {
+	e := startServer(t)
+
+	requestBody := map[string]interface{}{
+		"name":   "Test Goal",
+		"target": 5000,
+		"weight": 10,
+	}
+
+	e.POST("/user/999/goals").
+		WithJSON(requestBody).
+		Expect().
+		Status(404)
+}
+
+func TestCreateUserGoalInvalidInput(t *testing.T) {
+	e := startServer(t)
+
+	// Test with empty name
+	requestBody := map[string]interface{}{
+		"name":   "",
+		"target": 5000,
+		"weight": 10,
+	}
+
+	e.POST("/user/1/goals").
+		WithJSON(requestBody).
+		Expect().
+		Status(400)
+
+	// Test with missing fields
+	invalidRequest := map[string]interface{}{
+		"target": 5000,
+	}
+
+	e.POST("/user/1/goals").
+		WithJSON(invalidRequest).
+		Expect().
+		Status(400)
+}
+
+func TestCreateUserGoalBadId(t *testing.T) {
+	e := startServer(t)
+
+	requestBody := map[string]interface{}{
+		"name":   "Test Goal",
+		"target": 5000,
+		"weight": 10,
+	}
+
+	e.POST("/user/bad-id/goals").
+		WithJSON(requestBody).
+		Expect().
+		Status(400)
+}
diff --git a/backend/db.go b/backend/db.go
index 06fe17a..9f2c0f6 100644
--- a/backend/db.go
+++ b/backend/db.go
@@ -2,8 +2,9 @@ package main
 
 import (
 	"errors"
-	"gitea.seeseepuff.be/seeseemelk/mysqlite"
 	"log"
+
+	"gitea.seeseepuff.be/seeseemelk/mysqlite"
 )
 
 type Db struct {
@@ -86,3 +87,44 @@ func (db *Db) GetUserGoals(userId int) ([]Goal, error) {
 	}
 	return goals, nil
 }
+
+func (db *Db) CreateGoal(userId int, goal *CreateGoalRequest) (int, error) {
+	// Check if user exists before attempting to create a goal
+	exists, err := db.UserExists(userId)
+	if err != nil {
+		return 0, err
+	}
+	if !exists {
+		return 0, errors.New("user does not exist")
+	}
+
+	tx, err := db.db.Begin()
+	if err != nil {
+		return 0, err
+	}
+	defer tx.Rollback()
+
+	// Insert the new goal
+	err = tx.Query("insert into goals (user_id, name, target, progress, weight) values (?, ?, ?, 0, ?)").
+		Bind(userId, goal.Name, goal.Target, goal.Weight).
+		Exec()
+
+	if err != nil {
+		return 0, err
+	}
+
+	// Get the last inserted ID
+	var lastId int
+	err = tx.Query("select last_insert_rowid()").ScanSingle(&lastId)
+	if err != nil {
+		return 0, err
+	}
+
+	// Commit the transaction
+	err = tx.Commit()
+	if err != nil {
+		return 0, err
+	}
+
+	return lastId, nil
+}
diff --git a/backend/dto.go b/backend/dto.go
index 9d9ab5c..1b7ae67 100644
--- a/backend/dto.go
+++ b/backend/dto.go
@@ -24,3 +24,13 @@ type Goal struct {
 	Progress int    `json:"progress"`
 	Weight   int    `json:"weight"`
 }
+
+type CreateGoalRequest struct {
+	Name   string `json:"name"`
+	Target int    `json:"target"`
+	Weight int    `json:"weight"`
+}
+
+type CreateGoalResponse struct {
+	ID int `json:"id"`
+}
diff --git a/backend/main.go b/backend/main.go
index 3b30693..848d130 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -4,11 +4,12 @@ import (
 	"context"
 	"embed"
 	"errors"
-	"github.com/gin-gonic/gin"
 	"log"
 	"net/http"
 	"os"
 	"strconv"
+
+	"github.com/gin-gonic/gin"
 )
 
 //go:embed migrations/*.sql
@@ -92,6 +93,46 @@ func getUserGoals(c *gin.Context) {
 	c.IndentedJSON(http.StatusOK, goals)
 }
 
+func createUserGoal(c *gin.Context) {
+	userIdStr := c.Param("userId")
+	userId, err := strconv.Atoi(userIdStr)
+	if err != nil {
+		log.Printf("Invalid user ID: %v", err)
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
+		return
+	}
+
+	// Parse request body
+	var goalRequest CreateGoalRequest
+	if err := c.ShouldBindJSON(&goalRequest); err != nil {
+		log.Printf("Error parsing request body: %v", err)
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
+		return
+	}
+
+	// Validate request
+	if goalRequest.Name == "" {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Goal name cannot be empty"})
+		return
+	}
+
+	// Create goal in database
+	goalId, err := db.CreateGoal(userId, &goalRequest)
+	if err != nil {
+		log.Printf("Error creating goal: %v", err)
+		if err.Error() == "user does not exist" {
+			c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
+		} else {
+			c.JSON(http.StatusBadRequest, gin.H{"error": "Could not create goal"})
+		}
+		return
+	}
+
+	// Return created goal ID
+	response := CreateGoalResponse{ID: goalId}
+	c.IndentedJSON(http.StatusCreated, response)
+}
+
 /*
 *
 Initialises the database, and then starts the server.
@@ -105,6 +146,7 @@ func start(ctx context.Context, config *ServerConfig) {
 	router.GET("/api/users", getUsers)
 	router.GET("/api/user/:userId", getUser)
 	router.GET("/api/user/:userId/goals", getUserGoals)
+	router.POST("/api/user/:userId/goals", createUserGoal)
 
 	srv := &http.Server{
 		Addr:    ":" + config.Port,