diff --git a/BackendTests/Services/ActionServiceTests.cs b/BackendTests/Services/ActionServiceTests.cs
index e8ee31c..097b86d 100644
--- a/BackendTests/Services/ActionServiceTests.cs
+++ b/BackendTests/Services/ActionServiceTests.cs
@@ -65,7 +65,7 @@ public class ActionServiceTests
     [Test]
     public void RollDice_ReturnsListOfIntegers()
     {
-        _service.Group = new pacMan.Services.Game(new Queue<DirectionalPosition>());
+        _service.Game = new pacMan.Services.Game(new Queue<DirectionalPosition>());
         var dices = _service.RollDice();
         Assert.Multiple(() =>
         {
@@ -182,7 +182,7 @@ public class ActionServiceTests
     {
         var group = new pacMan.Services.Game(new Queue<DirectionalPosition>())
             { Players = { _blackPlayer, _whitePlayer } };
-        _service.Group = group;
+        _service.Game = group;
         _service.Player = _blackPlayer;
 
         var result = _service.Ready();
@@ -204,14 +204,14 @@ public class ActionServiceTests
     [Test]
     public void FindNextPlayer_NoPlayers()
     {
-        _service.Group = new pacMan.Services.Game(new Queue<DirectionalPosition>());
+        _service.Game = new pacMan.Services.Game(new Queue<DirectionalPosition>());
         Assert.Throws<InvalidOperationException>(() => _service.FindNextPlayer());
     }
 
     [Test]
     public void FindNextPlayer_OnePlayer()
     {
-        _service.Group =
+        _service.Game =
             new pacMan.Services.Game(new Queue<DirectionalPosition>(
                     new[] { new DirectionalPosition { At = new Position { X = 3, Y = 3 }, Direction = Direction.Up } }))
                 { Players = { _whitePlayer } };
@@ -223,7 +223,7 @@ public class ActionServiceTests
     [Test]
     public void FindNextPlayer_TwoPlayers()
     {
-        _service.Group = new pacMan.Services.Game(new Queue<DirectionalPosition>(
+        _service.Game = new pacMan.Services.Game(new Queue<DirectionalPosition>(
             new[]
             {
                 new DirectionalPosition { At = new Position { X = 3, Y = 3 }, Direction = Direction.Up },
diff --git a/pac-man-board-game/ClientApp/src/components/navMenu.tsx b/pac-man-board-game/ClientApp/src/components/navMenu.tsx
index 294b01a..7299e80 100644
--- a/pac-man-board-game/ClientApp/src/components/navMenu.tsx
+++ b/pac-man-board-game/ClientApp/src/components/navMenu.tsx
@@ -18,6 +18,7 @@ const NavMenu: Component = () => {
           <ul className="navbar-nav flex-grow">
             <Link className="text-dark" to="/">Home</Link>
             <Link className="text-dark" to="/counter">Counter</Link>
+            <Link to={"/lobby"}>Lobby</Link>
           </ul>
         </div>
       </nav>
diff --git a/pac-man-board-game/ClientApp/src/pages/game.tsx b/pac-man-board-game/ClientApp/src/pages/game.tsx
index f0173b6..8aee7e0 100644
--- a/pac-man-board-game/ClientApp/src/pages/game.tsx
+++ b/pac-man-board-game/ClientApp/src/pages/game.tsx
@@ -1,10 +1,10 @@
-import React, {useEffect} from "react";
+import React, {FC, useEffect} from "react";
 import {GameComponent} from "../components/gameComponent";
 import {useAtomValue} from "jotai";
 import {thisPlayerAtom} from "../utils/state";
 import {testMap} from "../game/map";
 
-const Game: Component = () => {
+const Game: FC = () => { // TODO gameId in path
   const player = useAtomValue(thisPlayerAtom);
 
   useEffect(() => {
diff --git a/pac-man-board-game/ClientApp/src/pages/lobby.tsx b/pac-man-board-game/ClientApp/src/pages/lobby.tsx
index e75917c..b75cb53 100644
--- a/pac-man-board-game/ClientApp/src/pages/lobby.tsx
+++ b/pac-man-board-game/ClientApp/src/pages/lobby.tsx
@@ -1,13 +1,14 @@
-import React, {Suspense} from "react";
+import React, {FC, Suspense} from "react";
 import {atom, useAtomValue} from "jotai";
 import {Button} from "../components/Button";
+import {thisPlayerAtom} from "../utils/state";
 
 const fetchAtom = atom(async () => {
-  const response = await fetch(import.meta.env.VITE_API_HTTP + "/allGames");
+  const response = await fetch(import.meta.env.VITE_API_HTTP + "/all");
   return await response.json() as Game[];
 });
-
-const LobbyPage: Component = () => (
+// TODO create game button
+const LobbyPage: FC = () => ( // TODO check if player is defined in storage, if not redirect to login
   <Suspense fallback={"Please wait"}>
     <GameTable className={"mx-auto"}/>
   </Suspense>
@@ -18,9 +19,28 @@ export default LobbyPage;
 const GameTable: Component = ({className}) => {
 
   const data = useAtomValue(fetchAtom);
+  const thisPlayer = useAtomValue(thisPlayerAtom);
 
-  function joinGame(gameId: string): void {
-    console.log("Joining game " + gameId); // TODO: Implement
+  async function joinGame(gameId: string): Promise<void> {
+    if (thisPlayer === undefined) throw new Error("Player is undefined");
+
+    console.debug("Joining game " + gameId);
+
+    const result = await fetch(import.meta.env.VITE_API_HTTP + "/join/" + gameId, {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json"
+      },
+      body: JSON.stringify(thisPlayer),
+    })
+
+    if (result.ok) {
+      console.debug("Joined game " + gameId, result.body);
+      // TODO redirect to game page
+    } else {
+      console.error("Failed to join game " + gameId, result.body);
+      // TODO show error message
+    }
   }
 
   return (
diff --git a/pac-man-board-game/Controllers/GameController.cs b/pac-man-board-game/Controllers/GameController.cs
index b93a266..87964ef 100644
--- a/pac-man-board-game/Controllers/GameController.cs
+++ b/pac-man-board-game/Controllers/GameController.cs
@@ -1,6 +1,8 @@
 using System.Net.WebSockets;
 using Microsoft.AspNetCore.Mvc;
+using pacMan.Exceptions;
 using pacMan.GameStuff;
+using pacMan.GameStuff.Items;
 using pacMan.Services;
 using pacMan.Utils;
 
@@ -23,13 +25,47 @@ public class GameController : GenericController // TODO reconnect using player i
     [HttpGet("connect")]
     public override async Task Accept() => await base.Accept();
 
-    [HttpGet("allGames")]
+    [HttpGet("all")]
     public IEnumerable<Game> GetAllGames()
     {
-        Logger.Log(LogLevel.Information, "Returning all games");
+        Logger.Log(LogLevel.Debug, "Returning all games");
         return _gameService.Games;
     }
 
+    [HttpPost("join/{gameId}")]
+    public IActionResult JoinGame(Guid gameId, [FromBody] Player player) // TODO what if player is in a game already?
+    {
+        Logger.Log(LogLevel.Debug, "Joining game {}", gameId);
+        try
+        {
+            _gameService.JoinById(gameId, player);
+            return Ok("Game joined successfully");
+        }
+        catch (GameNotFoundException e)
+        {
+            return NotFound(e.Message);
+        }
+        catch (Exception e)
+        {
+            return BadRequest(e.Message);
+        }
+    }
+
+    [HttpPost("createGame")]
+    public IActionResult CreateGame([FromBody] PlayerInfoData data)
+    {
+        Logger.Log(LogLevel.Debug, "Creating game");
+        try
+        {
+            var game = _gameService.CreateAndJoin(data.Player, data.Spawns);
+            return Created($"/{game.Id}", game);
+        }
+        catch (Exception e)
+        {
+            return BadRequest(e.Message); // TODO not necessary?
+        }
+    }
+
 
     protected override ArraySegment<byte> Run(WebSocketReceiveResult result, byte[] data)
     {
@@ -47,6 +83,7 @@ public class GameController : GenericController // TODO reconnect using player i
     protected override Task Echo()
     {
         _gameService.Connections += WsServiceOnFire;
+        // _actionService.Game.Connections += WsServiceOnFire;
         return base.Echo();
     }
 
diff --git a/pac-man-board-game/Services/ActionService.cs b/pac-man-board-game/Services/ActionService.cs
index 4e0dd26..dbefc29 100644
--- a/pac-man-board-game/Services/ActionService.cs
+++ b/pac-man-board-game/Services/ActionService.cs
@@ -1,4 +1,5 @@
 using System.Text.Json;
+using System.Text.Json.Serialization;
 using pacMan.GameStuff;
 using pacMan.GameStuff.Items;
 
@@ -7,7 +8,7 @@ namespace pacMan.Services;
 public interface IActionService
 {
     IPlayer Player { set; }
-    Game Group { set; }
+    Game Game { set; }
     void DoAction(ActionMessage message);
     List<int> RollDice();
     List<IPlayer> SetPlayerInfo(JsonElement? jsonElement);
@@ -28,7 +29,7 @@ public class ActionService : IActionService
         _gameService = gameService;
     }
 
-    public Game? Group { get; set; }
+    public Game? Game { get; set; }
 
     public IPlayer? Player { get; set; }
 
@@ -47,8 +48,8 @@ public class ActionService : IActionService
 
     public List<int> RollDice()
     {
-        Group?.DiceCup.Roll();
-        var rolls = Group?.DiceCup.Values ?? new List<int>();
+        Game?.DiceCup.Roll();
+        var rolls = Game?.DiceCup.Values ?? new List<int>();
         _logger.Log(LogLevel.Information, "Rolled [{}]", string.Join(", ", rolls));
 
         return rolls;
@@ -66,27 +67,27 @@ public class ActionService : IActionService
         {
             player.State = group.IsGameStarted ? State.InGame : State.WaitingForPlayers;
             Player = player;
-            Group = group;
+            Game = group;
             // TODO send missing data: Dices, CurrentPlayer, Ghosts
         }
         else
         {
-            Group = _gameService.AddPlayer(Player, data.Spawns);
+            Game = _gameService.AddPlayer(Player, data.Spawns);
         }
 
-        return Group.Players;
+        return Game.Players;
     }
 
     public object Ready()
     {
         object data;
-        if (Player != null && Group != null)
+        if (Player != null && Game != null)
         {
-            var players = Group.SetReady(Player).ToArray();
+            var players = Game.SetReady(Player).ToArray();
             // TODO roll to start
-            Group.Shuffle();
+            Game.Shuffle();
             var allReady = players.All(p => p.State == State.Ready);
-            if (allReady) Group.SetAllInGame();
+            if (allReady) Game.SetAllInGame();
             data = new ReadyData { AllReady = allReady, Players = players };
         }
         else
@@ -97,7 +98,7 @@ public class ActionService : IActionService
         return data;
     }
 
-    public string FindNextPlayer() => Group?.NextPlayer().UserName ?? "Error: No group found";
+    public string FindNextPlayer() => Game?.NextPlayer().UserName ?? "Error: No group found";
 
     public void Disconnect()
     {
@@ -106,9 +107,9 @@ public class ActionService : IActionService
 
     public object? HandleMoveCharacter(JsonElement? jsonElement)
     {
-        if (Group != null && jsonElement.HasValue)
-            Group.Ghosts = jsonElement.Value.GetProperty("Ghosts").Deserialize<List<Character>>() ??
-                           throw new JsonException("Ghosts is null");
+        if (Game != null && jsonElement.HasValue)
+            Game.Ghosts = jsonElement.Value.GetProperty("Ghosts").Deserialize<List<Character>>() ??
+                          throw new JsonException("Ghosts is null");
 
         return jsonElement;
     }
@@ -116,12 +117,12 @@ public class ActionService : IActionService
 
 public struct PlayerInfoData
 {
-    public required Player Player { get; set; }
-    public required Queue<DirectionalPosition> Spawns { get; set; }
+    [JsonInclude] public required Player Player { get; init; }
+    [JsonInclude] public required Queue<DirectionalPosition> Spawns { get; init; }
 }
 
 public struct ReadyData
 {
-    public required bool AllReady { get; init; }
-    public required IEnumerable<IPlayer> Players { get; set; }
+    [JsonInclude] public required bool AllReady { get; init; }
+    [JsonInclude] public required IEnumerable<IPlayer> Players { get; set; }
 }