diff --git a/pyproject.toml b/pyproject.toml
index a007460..605ebde 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,6 +34,7 @@ dependencies = [
     "pygeofilter>=0.2.0,<0.3.0",
     "ciso8601~=2.3",
     "starlette-cramjam>=0.4,<0.5",
+    "aiocache",
 ]
 
 [project.optional-dependencies]
diff --git a/tests/conftest.py b/tests/conftest.py
index 951318e..9e2417e 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,7 +2,9 @@
 
 import os
 from contextlib import asynccontextmanager
+from typing import Any, Dict
 
+import aiocache
 import psycopg
 import pytest
 from pytest_postgresql.janitor import DatabaseJanitor
@@ -101,6 +103,17 @@ def database_url(database):
     return db_url
 
 
+def _setup_cache():
+    config: Dict[str, Any] = {
+        "cache": "aiocache.SimpleMemoryCache",
+        "serializer": {
+            "class": "aiocache.serializers.PickleSerializer",
+        },
+        "ttl": 100,
+    }
+    aiocache.caches.set_config({"default": config})
+
+
 def create_tipg_app(
     postgres_settings: PostgresSettings,
     db_settings: DatabaseSettings,
@@ -134,6 +147,7 @@ async def lifespan(app: FastAPI):
             spatial_extent=db_settings.spatial_extent,
             datetime_extent=db_settings.datetime_extent,
         )
+        _setup_cache()
         yield
         await close_db_connection(app)
 
@@ -175,6 +189,7 @@ def app(database_url, monkeypatch):
     monkeypatch.setenv("TIPG_DEFAULT_MINZOOM", str(5))
     monkeypatch.setenv("TIPG_DEFAULT_MAXZOOM", str(12))
 
+    monkeypatch.setenv("TIPG_CACHE_DISABLE", "TRUE")
     monkeypatch.setenv("TIPG_DEBUG", "TRUE")
 
     from tipg.main import app, db_settings, postgres_settings
diff --git a/tipg/collections.py b/tipg/collections.py
index 4592cd7..1caf6a2 100644
--- a/tipg/collections.py
+++ b/tipg/collections.py
@@ -1,10 +1,13 @@
 """tipg.dbmodel: database events."""
 
 import datetime
+import hashlib
+import json
 import re
 from functools import lru_cache
 from typing import Any, Dict, List, Optional, Tuple, TypedDict, Union
 
+from aiocache import cached
 from buildpg import RawDangerous as raw
 from buildpg import asyncpg, clauses
 from buildpg import funcs as pg_funcs
@@ -794,6 +797,14 @@ async def features(
             prev=max(offset - limit, 0) if offset else None,
         )
 
+    @cached(
+        key_builder=lambda _f,
+        self,
+        pool,
+        tms,
+        tile,
+        **kwargs: f"{self.id}-{tms.id}-{tile.x}-{tile.y}-{tile.z}-{hashlib.md5(json.dumps(kwargs, sort_keys=True).encode('utf-8')).hexdigest()}",
+    )
     async def get_tile(
         self,
         *,
@@ -813,6 +824,7 @@ async def get_tile(
         limit: Optional[int] = None,
     ):
         """Build query to get Vector Tile."""
+        print(f"{tile.x}-{tile.y}-{tile.z}")
         limit = limit or mvt_settings.max_features_per_tile
 
         geometry_column = self.get_geometry_column(geom)
diff --git a/tipg/factory.py b/tipg/factory.py
index 1845787..c2022c7 100644
--- a/tipg/factory.py
+++ b/tipg/factory.py
@@ -45,7 +45,7 @@
 from tipg.errors import MissingGeometryColumn, NoPrimaryKey, NotFound
 from tipg.resources.enums import MediaType
 from tipg.resources.response import GeoJSONResponse, SchemaJSONResponse, orjsonDumps
-from tipg.settings import FeaturesSettings, MVTSettings, TMSSettings
+from tipg.settings import CacheSettings, FeaturesSettings, MVTSettings, TMSSettings
 
 from fastapi import APIRouter, Depends, Path, Query
 from fastapi.responses import ORJSONResponse
@@ -59,6 +59,7 @@
 tms_settings = TMSSettings()
 mvt_settings = MVTSettings()
 features_settings = FeaturesSettings()
+cache_settings = CacheSettings()
 
 
 jinja2_env = jinja2.Environment(
@@ -1604,6 +1605,8 @@ async def collection_get_tile(
                 limit=limit,
                 geom=geom_column,
                 dt=datetime_column,
+                cache_write=cache_settings.disable is False,
+                cache_read=cache_settings.disable is False,
             )
 
             return Response(bytes(tile), media_type=MediaType.mvt.value)
diff --git a/tipg/main.py b/tipg/main.py
index a98186e..8f88717 100644
--- a/tipg/main.py
+++ b/tipg/main.py
@@ -1,8 +1,9 @@
 """tipg app."""
 
 from contextlib import asynccontextmanager
-from typing import Any, List
+from typing import Any, Dict, List
 
+import aiocache
 import jinja2
 
 from tipg import __version__ as tipg_version
@@ -13,6 +14,7 @@
 from tipg.middleware import CacheControlMiddleware, CatalogUpdateMiddleware
 from tipg.settings import (
     APISettings,
+    CacheSettings,
     CustomSQLSettings,
     DatabaseSettings,
     PostgresSettings,
@@ -28,6 +30,21 @@
 postgres_settings = PostgresSettings()
 db_settings = DatabaseSettings()
 custom_sql_settings = CustomSQLSettings()
+cache_settings = CacheSettings()
+
+
+def setup_cache():
+    """Setup aiocache."""
+    config: Dict[str, Any] = {
+        "cache": "aiocache.SimpleMemoryCache",
+        "serializer": {
+            "class": "aiocache.serializers.PickleSerializer",
+        },
+    }
+    if cache_settings.ttl is not None:
+        config["ttl"] = cache_settings.ttl
+
+    aiocache.caches.set_config({"default": config})
 
 
 @asynccontextmanager
@@ -56,6 +73,8 @@ async def lifespan(app: FastAPI):
         datetime_extent=db_settings.datetime_extent,
     )
 
+    setup_cache()
+
     yield
 
     # Close the Connection Pool
diff --git a/tipg/settings.py b/tipg/settings.py
index f57855b..c255424 100644
--- a/tipg/settings.py
+++ b/tipg/settings.py
@@ -192,3 +192,19 @@ def sql_files(self) -> Optional[List[pathlib.Path]]:
             return list(self.custom_sql_directory.glob("*.sql"))
 
         return None
+
+
+class CacheSettings(BaseSettings):
+    """Cache settings"""
+
+    # TTL of the cache in seconds
+    ttl: int = 300
+
+    # Whether or not caching is enabled
+    disable: bool = False
+
+    model_config = {
+        "env_prefix": "TIPG_CACHE_",
+        "env_file": ".env",
+        "extra": "ignore",
+    }