diff --git a/src/Controllers/AuthController.php b/src/Controllers/AuthController.php
index 235a3278ebea824a651bcb1f0fa51dd8a397fa7c..97a5bb1b70434ba36e657d98dfe120dfdf5d7aff 100644
--- a/src/Controllers/AuthController.php
+++ b/src/Controllers/AuthController.php
@@ -17,10 +17,6 @@ use app\Utils\Toast;
 class AuthController extends Controller
 {
 
-  public function index()
-  {
-    $this->render('index');
-  }
 
   public function login()
   {
diff --git a/src/Controllers/DormController.php b/src/Controllers/DormController.php
index 6c1f169b5d428e4d89fbd7df6aa773c53762d473..d4509214903e73f80e08192971b424bfbc60b255 100644
--- a/src/Controllers/DormController.php
+++ b/src/Controllers/DormController.php
@@ -15,6 +15,16 @@ use app\Utils\FileManager;
 
 class DormController extends Controller
 {
+
+  public function list()
+  {
+    $page = Request::getPage();
+    $data = Dorm::getAllFiltered($page);
+    $data["page"] = $page;
+    $this->render('index', $data);
+  }
+
+
   public function create()
   {
     $form = new Validation([
diff --git a/src/Core/Application.php b/src/Core/Application.php
index 563a07596c957af78497880f89d73a4418704a7e..c24a4eb9b6af3a912071003097e62a8ef5ca53df 100644
--- a/src/Core/Application.php
+++ b/src/Core/Application.php
@@ -32,7 +32,7 @@ class Application
   public function configureRoutes()
   {
     $this->router = new Router();
-    $this->router->get('/', [], AuthController::class, 'index');
+    $this->router->get('/', [], DormController::class, 'list');
     $this->router->methods(["GET", "POST"], '/login', [],  AuthController::class, 'login');
     $this->router->methods(["GET", "POST"], '/register', [],  AuthController::class, 'register');
     $this->router->get('/logout', [],  AuthController::class, 'logout');
@@ -46,4 +46,4 @@ class Application
     $this->router->methods(["GET", "POST"], "/dorms/create", [AdminOnly::class], DormController::class, 'create');
     $this->router->methods(["GET", "POST"], "/dorms/{dormId}/media", [AdminOnly::class], DormController::class, 'media');
   }
-}
\ No newline at end of file
+}
diff --git a/src/Core/Request.php b/src/Core/Request.php
index d5798187ea5a3679f5a994716ccb618bae9d35f7..35f1662d73315205039c7c11b10cdca6302c019c 100644
--- a/src/Core/Request.php
+++ b/src/Core/Request.php
@@ -59,4 +59,14 @@ class Request
   {
     return $_FILES[$fieldName] ?? null;
   }
+
+  public static function getPage()
+  {
+    $page = $_GET['page'] ?? "1";
+    try {
+      return intval($page);
+    } catch (\Throwable $th) {
+      return 1;
+    }
+  }
 }
diff --git a/src/Models/BaseModel.php b/src/Models/BaseModel.php
index 0e341c7ca9e1bb0805f7725b901a2e64388c62a0..886477f946c65360b0596b8e81194fb2df303ccb 100644
--- a/src/Models/BaseModel.php
+++ b/src/Models/BaseModel.php
@@ -4,17 +4,18 @@ namespace app\Models;
 
 use app\Core\Application;
 use app\Core\Database;
+use PDO;
 
 #[\AllowDynamicProperties]
 class BaseModel
 {
-  protected static Database $db;
+  protected static PDO $db;
   protected static string $table = "";
   protected static string $primaryKey = "";
 
   public static function connect()
   {
-    self::$db = Application::$db;
+    self::$db = Application::$db->pdo;
   }
 
   public static function toModel($data): self
@@ -28,7 +29,7 @@ class BaseModel
     return $model;
   }
 
-  protected static function toModelArray($data)
+  public static function toModelArray($data)
   {
     $models = [];
     foreach ($data as $row) {
@@ -39,14 +40,14 @@ class BaseModel
 
   public static function all($orderBy = null)
   {
-    $stmt = self::$db->pdo->prepare("SELECT * FROM " . static::$table . ($orderBy ? " ORDER BY " . $orderBy : ""));
+    $stmt = self::$db->prepare("SELECT * FROM " . static::$table . ($orderBy ? " ORDER BY " . $orderBy : ""));
     $stmt->execute();
     return self::toModelArray($stmt->fetchAll());
   }
 
   public static function findById(int $id): static
   {
-    $stmt = self::$db->pdo->prepare("SELECT * FROM " . static::$table . " WHERE " . static::$primaryKey . " = :id");
+    $stmt = self::$db->prepare("SELECT * FROM " . static::$table . " WHERE " . static::$primaryKey . " = :id");
     $stmt->bindValue(":id", $id);
     $stmt->execute();
     $data = $stmt->fetch();
@@ -58,7 +59,7 @@ class BaseModel
 
   public static function deleteById(int $id)
   {
-    $stmt = self::$db->pdo->prepare("DELETE FROM " . static::$table . " WHERE " . static::$primaryKey . " = :id");
+    $stmt = self::$db->prepare("DELETE FROM " . static::$table . " WHERE " . static::$primaryKey . " = :id");
     $stmt->bindValue(":id", $id);
     $stmt->execute();
     return $stmt->rowCount();
@@ -73,7 +74,7 @@ class BaseModel
     }
     $where = substr($where, 0, -5);
 
-    $stmt = self::$db->pdo->prepare("SELECT * FROM " . static::$table . " WHERE " . $where);
+    $stmt = self::$db->prepare("SELECT * FROM " . static::$table . " WHERE " . $where);
     foreach ($arrFilter as $key => $value) {
       $stmt->bindValue(":" . $key, $value);
     }
@@ -90,7 +91,7 @@ class BaseModel
     }
     $where = substr($where, 0, -5);
 
-    $stmt = self::$db->pdo->prepare("SELECT * FROM " . static::$table . " WHERE " . $where . " LIMIT 1");
+    $stmt = self::$db->prepare("SELECT * FROM " . static::$table . " WHERE " . $where . " LIMIT 1");
     foreach ($arrFilter as $key => $value) {
       $stmt->bindValue(":" . $key, $value);
     }
@@ -122,7 +123,7 @@ class BaseModel
 
     $sql = "INSERT INTO " . static::$table . " (" . implode(",", $keys) . ") VALUES (:" . implode(",:", $keys) . ") RETURNING " . static::$primaryKey . ";";
 
-    $stmt = self::$db->pdo->prepare($sql);
+    $stmt = self::$db->prepare($sql);
 
     foreach ($fields as $key => $value) {
       $stmt->bindValue(":" . $key, $value);
@@ -149,7 +150,7 @@ class BaseModel
     $sql = substr($sql, 0, -2);
     $sql .= " WHERE " . static::$primaryKey . " = :" . static::$primaryKey . ";";
 
-    $stmt = self::$db->pdo->prepare($sql);
+    $stmt = self::$db->prepare($sql);
 
     foreach ($fields as $key => $value) {
       $stmt->bindValue(":" . $key, $value);
@@ -163,7 +164,7 @@ class BaseModel
   {
     $sql = "DELETE FROM " . static::$table . " WHERE " . static::$primaryKey . " = :" . static::$primaryKey . ";";
 
-    $stmt = self::$db->pdo->prepare($sql);
+    $stmt = self::$db->prepare($sql);
 
     $stmt->bindValue(":" . static::$primaryKey, $this->{static::$primaryKey});
 
diff --git a/src/Models/Dorm.php b/src/Models/Dorm.php
index 3157abf1aa9222d3c1e90e7da8f82ac47e5e9a81..89edd8d809951c689964b5a566ff10cc58f62a44 100644
--- a/src/Models/Dorm.php
+++ b/src/Models/Dorm.php
@@ -14,4 +14,57 @@ class Dorm extends BaseModel
   public int $owner_id;
   protected static string $table = "dorms";
   protected static string $primaryKey = "dorm_id";
+
+  public static function getAllFiltered($page = 1, $orderBy = "dorm_id DESC", $search = "")
+  {
+    $limit = 4;
+    $offset = ($page - 1) * $limit;
+
+    $stmt = self::$db->prepare(
+      $search ?
+        "SELECT * FROM dorms WHERE name ILIKE :1 ORDER BY :2 LIMIT :3 OFFSET :4" :
+        "SELECT * FROM dorms ORDER BY :2 LIMIT :3 OFFSET :4"
+    );
+
+    if ($search) {
+      $stmt->bindValue(":1", "%{$search}%");
+    }
+    $stmt->bindValue(":2", $orderBy);
+    $stmt->bindValue(":3", $limit);
+    $stmt->bindValue(":4", $offset);
+
+    $stmt->execute();
+    $dorms = self::toModelArray($stmt->fetchAll());
+
+    $stmt = self::$db->prepare(
+      $search ?
+        "SELECT COUNT(dorm_id) FROM dorms WHERE name ILIKE :1" :
+        "SELECT COUNT(dorm_id) FROM dorms"
+    );
+
+    if ($search) {
+      $stmt->bindValue(":1", "%{$search}%");
+    }
+
+    $stmt->execute();
+
+    $count = $stmt->fetchColumn();
+    $totalPage = ceil($count / $limit);
+
+    $dormIds = array_map(fn ($dorm) => $dorm->dorm_id, $dorms);
+
+    $stmt = self::$db->prepare("SELECT * FROM medias
+      NATURAL JOIN (
+        SELECT MIN(media_id) AS media_id, dorm_id FROM medias WHERE type = 'photo' AND dorm_id IN (" . implode(",", $dormIds) . ") GROUP BY dorm_id
+        ) AS m");
+    $stmt->execute();
+
+    $medias = Media::toModelArray($stmt->fetchAll());
+    $medias = array_reduce($medias, function ($acc, $media) {
+      $acc[$media->dorm_id] = $media;
+      return $acc;
+    }, []);
+
+    return ["dorms" => $dorms, "medias" => $medias, "totalPage" => $totalPage];
+  }
 }
diff --git a/src/Views/index.php b/src/Views/index.php
index 4cf6a4336eb30b64950777f780252099c702176e..077aa691e3124c3f69a4c8a4c3e35284a71cba3d 100644
--- a/src/Views/index.php
+++ b/src/Views/index.php
@@ -7,8 +7,29 @@
   <p class="hero-desc">Dapatkan infonya dan langsung sewa di MyKos.
   </p>
   <? if ($user->is_admin ?? false) : ?>
-    <div class="cta-admin">
-      <a href="/dorms/create" class="btn btn-primary">Tambah Kos</a>
-    </div>
+  <div class="cta-admin">
+    <a href="/dorms/create" class="btn btn-primary">Tambah Kos</a>
+  </div>
   <? endif; ?>
+  <ul class="dorm_list">
+    <? foreach ($dorms as $dorm) : ?>
+    <li class="dorm_item">
+      <a href="/dorms/<?= $dorm->dorm_id ?>">
+        <? $media = array_key_exists($dorm->dorm_id, $medias) ? $medias[$dorm->dorm_id] : null ?>
+        <? if ($media) : ?>
+        <img class="dorm_image" src="<?= $media->endpoint ?>" alt="<?= $media->alt_text ?>" />
+        <? else : ?>
+        <div class="dorm_image"></div>
+        <? endif; ?>
+        <div class="dorm_item__content">
+          <h3 class="dorm_item__title line-clamp-1"><?= $dorm->name ?></h3>
+          <p class="dorm_item__city"><?= $dorm->city ?></p>
+          <p class="dorm_item__price">Rp<?= number_format($dorm->price, 0, ',', '.') ?>
+            <span> / bulan</span>
+          </p>
+        </div>
+      </a>
+    </li>
+    <? endforeach; ?>
+  </ul>
 </section>
\ No newline at end of file
diff --git a/src/public/static/styles/home.css b/src/public/static/styles/home.css
index 6d0bf26b1d81e48af1e592d7469b84691b664e1c..d9305c5fb5dce3009ba152d1a6e13bd61fc5a6f8 100644
--- a/src/public/static/styles/home.css
+++ b/src/public/static/styles/home.css
@@ -21,3 +21,52 @@
   min-width: 10rem;
   text-align: center;
 }
+
+.dorm_list {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 2rem;
+  margin-top: 4rem;
+}
+
+.dorm_item__title {
+  color: #303030;
+  font-size: var(--text-sm);
+  margin-top: 0.5rem;
+}
+
+.dorm_item__city {
+  color: #404040;
+  font-size: var(--text-sm);
+  font-weight: 700;
+  margin-top: 0.5rem;
+}
+
+.dorm_item__price {
+  color: #303030;
+  font-size: var(--text-base);
+  font-weight: 700;
+  margin-top: 0.5rem;
+}
+
+.dorm_item__price span {
+  font-weight: 400;
+}
+
+.dorm_image {
+  width: 100%;
+  aspect-ratio: 4/3;
+  object-fit: cover;
+  background-color: white;
+  border-radius: 1rem;
+  border: 1px solid var(--color-gray-darker);
+  transition: all 0.1s ease-in-out;
+}
+
+.dorm_item:hover .dorm_image {
+  transform: scale(1.05);
+}
+
+div.dorm_image {
+  background-color: var(--color-gray-darker);
+}
diff --git a/src/public/static/styles/main.css b/src/public/static/styles/main.css
index 7995ecda11e0819e0dd46e95088345a14ff07699..1d5b2333e4e031f0482f0a7ef8b114bfbdbb9aad 100644
--- a/src/public/static/styles/main.css
+++ b/src/public/static/styles/main.css
@@ -17,6 +17,13 @@ html {
   --text-sm: 0.875rem;
 }
 
+.line-clamp-1 {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 1;
+}
+
 body {
   min-height: 100%;
   display: flex;
@@ -385,7 +392,7 @@ td {
 .slides-container {
   max-width: 100%;
   width: 100%;
-  aspect-ratio: 16 / 9;
+  aspect-ratio: 4 / 3;
   display: flex;
   list-style: none;
   margin: 0;
@@ -403,7 +410,7 @@ td {
 video {
   width: 100%;
   height: 100%;
-  object-fit: contain;
+  object-fit: cover;
 }
 
 .slides-container {