diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 55b6cf1044e546916263274b79b0785a86b9abcb..713e7a30ec2f530d7a3fb96cc2617e37a8449947 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -60,6 +60,8 @@ repos:
       - psycopg2==2.9.1
       - sqlalchemy2-stubs==0.0.2a4
       - types-requests==2.25.0
+      - types-tabulate==0.8.0
+      - types-termcolor==1.1.0
     exclude: ^migrations/
 
 #- repo: https://github.com/pre-commit/mirrors-pylint
diff --git a/esap_client/clients/db_client.py b/esap_client/clients/db_client.py
index 85219c311ddaa01e45b20fcb888ead52c1fbc25d..efda4a9dbff47686b0dbb2a511a726d54b92f076 100644
--- a/esap_client/clients/db_client.py
+++ b/esap_client/clients/db_client.py
@@ -1,6 +1,6 @@
 """This module provides a client to ESAP-DB."""
 from ..config import settings
-from ..helpers import raise_for_status
+from ..helpers import print_table, raise_for_status
 from ..models import Project, ResourceCollection
 from ..sessions import BasedSession
 
@@ -34,3 +34,8 @@ def create_project(
         response = self.session.post('/projects', project)
         raise_for_status(response)
         return Project.deserialize(response.json())
+
+    def describe(self) -> None:
+        """Prints the projects available to the user through the client."""
+        projects = ', '.join(_.name.split('.')[-1] for _ in self.projects) or '[]'
+        print_table(projects=projects)
diff --git a/esap_client/helpers.py b/esap_client/helpers.py
index 18f5d385ae6e452dcb74ffcf21e72f8a8ab50113..57d3aeba715b7ed5abdd8922b6c2d77b70d4d3ae 100644
--- a/esap_client/helpers.py
+++ b/esap_client/helpers.py
@@ -2,6 +2,8 @@
 from typing import Type
 
 from requests import Response
+from tabulate import tabulate
+from termcolor import colored
 
 from .exceptions import ESAPClientError, ESAPError, ESAPServerError
 
@@ -20,3 +22,9 @@ def raise_for_status(response: Response) -> None:
     if isinstance(error, dict) and 'detail' in error:
         error = error['detail']
     raise cls(f'HTTP Error {response.status_code}: {error}')
+
+
+def print_table(**keywords: str) -> None:
+    """Prints a colorful table."""
+    table = [(colored(k.capitalize(), 'red'), v) for k, v in keywords.items()]
+    print(tabulate(table, tablefmt='plain'))
diff --git a/esap_client/models/datasets.py b/esap_client/models/datasets.py
index bb50e45da604bf58846ac959263dad1bcd97108a..8637bfcab6c8a4eea4bdb04dd9fda7389620bf59 100644
--- a/esap_client/models/datasets.py
+++ b/esap_client/models/datasets.py
@@ -7,7 +7,7 @@
 
 import pandas as pd
 
-from ..helpers import raise_for_status
+from ..helpers import print_table, raise_for_status
 from ..sessions import BasedSession
 from .collections import ResourceCollection
 from .tables import Table
@@ -52,7 +52,11 @@ def tables(self) -> ResourceCollection[Table]:
         )
 
     def create_table_from(
-        self, source: Union[str, pd.DataFrame], name: Optional[str] = None, description: str = '', **keywords
+        self,
+        source: Union[str, pd.DataFrame],
+        name: Optional[str] = None,
+        description: str = '',
+        **keywords: Any,
     ) -> Table:
         """Creates a table from a Pandas DataFrame."""
         if not isinstance(source, (str, pd.DataFrame)):
@@ -119,3 +123,13 @@ def create_table_from_esap_gateway_query(
         response = session.post('/esap-gateway-operations', payload)
         raise_for_status(response)
         return Table.deserialize(response.json())
+
+    def describe(self) -> None:
+        """Prints information associated with the dataset."""
+        infos = {
+            'name': self.name,
+            'description': self.description,
+        }
+        tables = ', '.join(_.name.split('.')[-1] for _ in self.tables)
+        infos['tables'] = tables or '[]'
+        print_table(**infos)
diff --git a/esap_client/models/projects.py b/esap_client/models/projects.py
index 219ad989705acd5ca1b0ce6bf3310371cdbfe75f..280910bc47450f37c930476cdc7b988b93e23b1e 100644
--- a/esap_client/models/projects.py
+++ b/esap_client/models/projects.py
@@ -4,7 +4,7 @@
 from typing import Any
 
 from ..config import settings
-from ..helpers import raise_for_status
+from ..helpers import print_table, raise_for_status
 from ..sessions import BasedSession
 from .collections import ResourceCollection
 from .datasets import Dataset
@@ -13,12 +13,13 @@
 class Project:
     """The project defines a scope, in which are stored collections of database tables."""  # noqa
 
-    def __init__(self, name: str, description: str, max_size: int) -> None:
+    def __init__(self, name: str, description: str, type: str, max_size: int) -> None:
         """The `Project` constructor."""
         if '.' in name:
             raise ValueError("Project names cannot contain the character '.'")
         self.name = name
         self.description = description
+        self.type = type
         self.max_size = max_size
         self.session = BasedSession(f'/projects/{name}')
 
@@ -46,8 +47,9 @@ def deserialize(cls, project: dict) -> Project:
         """Deserializes a dict-like project."""
         name = project['name']
         description = project['description']
+        type = project['type']
         max_size = project.get('max_size', settings.DEFAULT_PROJECT_MAX_SIZE)
-        return Project(name, description, max_size)
+        return Project(name, description, type, max_size)
 
     def create_dataset(self, name: str, description: str = '') -> Dataset:
         """Creates a dataset inside this project."""
@@ -63,3 +65,16 @@ def create_dataset(self, name: str, description: str = '') -> Dataset:
     def datasets(self) -> ResourceCollection[Dataset]:
         """The member datasets of this project."""
         return ResourceCollection[Dataset](f'/projects/{self.name}/datasets', Dataset)
+
+    def describe(self) -> None:
+        """Prints information associated with the project."""
+        infos = {
+            'name': self.name,
+            'description': self.description,
+            'type': self.type,
+        }
+        if self.type == 'user':
+            infos['max size'] = f'{self.max_size / 2 ** 30} GiB'
+        datasets = ', '.join(_.name.split('.')[-1] for _ in self.datasets)
+        infos['datasets'] = datasets or '[]'
+        print_table(**infos)
diff --git a/esap_client/models/tables.py b/esap_client/models/tables.py
index d3a82e0d263eebd0a150349f92b5a28357cd023e..6a8b6c9048475ea4ee4a7e3ad7abf5096d85fbea 100644
--- a/esap_client/models/tables.py
+++ b/esap_client/models/tables.py
@@ -5,6 +5,7 @@
 
 import pandas as pd
 
+from ..helpers import print_table, raise_for_status
 from ..sessions import BasedSession
 
 
@@ -26,6 +27,12 @@ def __eq__(self, other: Any) -> bool:
             return NotImplemented
         return self.name == other.name and self.description == other.description
 
+    def __len__(self) -> int:
+        """Returns the number of entries in the table."""
+        response = self.session.post(':getLength')
+        raise_for_status(response)
+        return response.json()
+
     def __repr__(self) -> str:
         """The string representation of a `Table`."""
         return f'<Table {self.name}>'
@@ -33,13 +40,14 @@ def __repr__(self) -> str:
     def delete(self) -> None:
         """Deletes this table."""
         response = self.session.delete('')
-        response.raise_for_status()
+        raise_for_status(response)
 
     @classmethod
     def deserialize(cls, table: dict) -> Table:
         """Deserializes a dict-like table."""
         return Table(table['name'], table['description'])
 
+    @property
     def column_names(self) -> List[str]:
         """Returns the names of the table columns."""
         return self.session.get('/column-names').json()
@@ -51,3 +59,16 @@ def _content(self) -> list[dict]:
     def aspandas(self) -> pd.DataFrame:
         """Returns the content of the table as a Pandas `DataFrame`."""
         return pd.read_json(f'{self.session.base_url}/content')
+
+    def describe(self) -> None:
+        """Prints information associated with the table."""
+        nrow = len(self)
+        row_str = 'row' if nrow < 2 else 'rows'
+        ncol = len(self.column_names)
+        col_str = 'column' if ncol < 2 else 'columns'
+        infos = {
+            'name': self.name,
+            'description': self.description,
+            'shape': f'{nrow} {row_str} x {ncol} {col_str}',
+        }
+        print_table(**infos)
diff --git a/poetry.lock b/poetry.lock
index 1c90e6072a66afab7d657cb803f38b8b57e75236..8b0e23a3f4a29860faaffba659d907d15d2ee850 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -578,6 +578,25 @@ category = "main"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "tabulate"
+version = "0.8.9"
+description = "Pretty-print tabular data"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+widechars = ["wcwidth"]
+
+[[package]]
+name = "termcolor"
+version = "1.1.0"
+description = "ANSII Color formatting for output in terminal."
+category = "main"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "toml"
 version = "0.10.2"
@@ -692,7 +711,7 @@ h11 = ">=0.9.0,<1"
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.9"
-content-hash = "54b6ee91552dc096f0511b64ba5c9c4571a9905b82dcb6af7c93ea5e3078bc79"
+content-hash = "c5a6013ec5017853114ad3f12bdcfd8b5c732ce206d08431a7886ac39372b42e"
 
 [metadata.files]
 anyio = [
@@ -1039,6 +1058,13 @@ sortedcontainers = [
     {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
     {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
 ]
+tabulate = [
+    {file = "tabulate-0.8.9-py3-none-any.whl", hash = "sha256:d7c013fe7abbc5e491394e10fa845f8f32fe54f8dc60c6622c6cf482d25d47e4"},
+    {file = "tabulate-0.8.9.tar.gz", hash = "sha256:eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7"},
+]
+termcolor = [
+    {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
+]
 toml = [
     {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
diff --git a/pyproject.toml b/pyproject.toml
index 031ff25e43d9d55a72dc0c298bca1f5ad24da796..09179311d39385439433d3ba8ad0890cad8c3937 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,6 +12,8 @@ asks = "^2.4.12"
 requests = "^2.25.1"
 pandas = "^1.2.5"
 pydantic = "^1.8.2"
+tabulate = "^0.8.9"
+termcolor = "^1.1.0"
 
 [tool.poetry.dev-dependencies]
 pytest = "^6.2.4"
@@ -28,7 +30,7 @@ skip-string-normalization = true
 
 [tool.isort]
 profile = "black"
-known_third_party = ["pandas", "pydantic", "pytest", "requests"]
+known_third_party = ["pandas", "pydantic", "pytest", "requests", "tabulate", "termcolor"]
 
 [tool.mypy]
 python_version = "3.9"