diff --git a/src/Configuration/ResourceConfig.php b/src/Configuration/ResourceConfig.php
index 06d7941..4bc998f 100644
--- a/src/Configuration/ResourceConfig.php
+++ b/src/Configuration/ResourceConfig.php
@@ -1,10 +1,10 @@
 <?php
 
 namespace Drupal\jsonapi\Configuration;
+
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 
-
 /**
  * Class ResourceConfig.
  *
diff --git a/src/Configuration/ResourceManager.php b/src/Configuration/ResourceManager.php
index 396dd18..50824bf 100644
--- a/src/Configuration/ResourceManager.php
+++ b/src/Configuration/ResourceManager.php
@@ -64,7 +64,8 @@ class ResourceManager implements ResourceManagerInterface {
     if ($this->all) {
       return $this->all;
     }
-    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_definition) {
+    $entity_type_ids = array_keys($this->entityTypeManager->getDefinitions());
+    foreach ($entity_type_ids as $entity_type_id) {
       // Add a ResourceConfig per bundle.
       $this->all = array_merge($this->all, array_map(function ($bundle) use ($entity_type_id) {
         $resource_config = new ResourceConfig($this->configFactory, $this->entityTypeManager);
diff --git a/src/Query/QueryBuilder.php b/src/Query/QueryBuilder.php
index 8a90a23..88fae18 100644
--- a/src/Query/QueryBuilder.php
+++ b/src/Query/QueryBuilder.php
@@ -20,7 +20,7 @@ class QueryBuilder implements QueryBuilderInterface {
   /**
    * The options to build with which to build a query.
    */
-  protected $options;
+  protected $options = [];
 
   /**
    * The entity type manager.
diff --git a/src/RequestHandler.php b/src/RequestHandler.php
index 4a48f82..c9cde41 100644
--- a/src/RequestHandler.php
+++ b/src/RequestHandler.php
@@ -59,11 +59,14 @@ class RequestHandler extends RestRequestHandler {
     $resource_manager = $this->container->get('jsonapi.resource.manager');
     /* @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
     $entity_type_manager = $this->container->get('entity_type.manager');
+    /* @var \Drupal\jsonapi\Query\QueryBuilderInterface $query_builder */
     $query_builder = $this->container->get('jsonapi.query_builder');
+    /* @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */
+    $field_manager = $this->container->get('entity_field.manager');
     $resource = new EntityResource($resource_manager->get(
       $route->getRequirement('_entity_type'),
       $route->getRequirement('_bundle')
-    ), $entity_type_manager, $query_builder);
+    ), $entity_type_manager, $query_builder, $field_manager);
     // Only add the unserialized data if there is something there.
     $extra_parameters = $unserialized ? [$unserialized, $request] : [$request];
     try {
diff --git a/src/Resource/EntityResource.php b/src/Resource/EntityResource.php
index d116cfe..a3380af 100644
--- a/src/Resource/EntityResource.php
+++ b/src/Resource/EntityResource.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\jsonapi\Resource;
 
+use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
@@ -13,6 +14,7 @@ use Drupal\jsonapi\Query\QueryBuilderInterface;
 use Drupal\rest\ResourceResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 
 /**
  * Class EntityResource.
@@ -36,6 +38,13 @@ class EntityResource implements EntityResourceInterface {
   protected $entityTypeManager;
 
   /**
+   * The field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $fieldManager;
+
+  /**
    * The query builder service.
    *
    * @var \Drupal\jsonapi\Query\QueryBuilderInterface
@@ -51,11 +60,14 @@ class EntityResource implements EntityResourceInterface {
    *   The entity type manager.
    * @param \Drupal\jsonapi\Query\QueryBuilderInterface $query_builder
    *   The query builder.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager
+   *   The entity type field manager.
    */
-  public function __construct(ResourceConfigInterface $resource_config, EntityTypeManagerInterface $entity_type_manager, QueryBuilderInterface $query_builder) {
+  public function __construct(ResourceConfigInterface $resource_config, EntityTypeManagerInterface $entity_type_manager, QueryBuilderInterface $query_builder, EntityFieldManagerInterface $field_manager) {
     $this->resourceConfig = $resource_config;
     $this->entityTypeManager = $entity_type_manager;
     $this->queryBuilder = $query_builder;
+    $this->fieldManager = $field_manager;
   }
 
   /**
@@ -88,6 +100,12 @@ class EntityResource implements EntityResourceInterface {
     $storage = $this->entityTypeManager->getStorage($entity_type_id);
     $entity_collection = new EntityCollection($storage->loadMultiple($results));
     $response = $this->buildWrappedResponse($entity_collection);
+
+    // When a new change to any entity in the resource happens, we cannot ensure
+    // the validity of this cached list. Add the list tag to deal with that.
+    $list_tag = $this->entityTypeManager->getDefinition('node')->getListCacheTags();
+    $response->getCacheableMetadata()->setCacheTags($list_tag);
+    // Add a cache tag for every entity in the list.
     foreach ($entity_collection as $entity) {
       $this->addCacheabilityMetadata($response, $entity);
     }
@@ -132,7 +150,8 @@ class EntityResource implements EntityResourceInterface {
    *   The filter parameter.
    */
   protected function applyFiltersForList(QueryInterface $query, JsonApiParamInterface $filter) {
-    foreach ($filter->get() as $field_name => $filter_info) {
+    foreach ($filter->get() as $public_name => $filter_info) {
+      $field_name = $this->queryFieldName($public_name);
       // Deal with multivalue operators.
       if ($filter_info['multivalue']) {
         // Add a single condition using all the values and one operator.
@@ -148,6 +167,49 @@ class EntityResource implements EntityResourceInterface {
   }
 
   /**
+   * Gets the field name in the backed usable with the entity condition.
+   *
+   * @param string $public_name
+   *   The field name as exposed in the API.
+   *
+   * @return string
+   *   The field name usable in \Drupal\Core\Entity\Query\QueryInterface.
+   */
+  protected function queryFieldName($public_name) {
+    // Right now we are exposing all the fields with the name they have in
+    // the Drupal backend. But this may change in the future.
+    if (strpos($public_name, '.') === FALSE) {
+      return $public_name;
+    }
+    // Turns 'uid.field_category.name' into
+    // 'uid.entity.field_category.entity.name'. This may be too simple, we may
+    // want to make sure that the public field is an entity reference first.
+    $parts = explode('.', $public_name);
+    // The last part of the chain is the referenced field, not a relationship.
+    $leave_field = array_pop($parts);
+    // Keep track of the entity type id.
+    $entity_type = $this->resourceConfig->getEntityTypeId();
+    // Prepare the exception only once.
+    $exception = new BadRequestHttpException('Invalid nested filtering.');
+    foreach ($parts as $field_name) {
+      if (!$definitions = $this->fieldManager->getFieldStorageDefinitions($entity_type)) {
+        throw $exception;
+      }
+      if (
+        empty($definitions[$field_name]) ||
+        $definitions[$field_name]->getType() != 'entity_reference'
+      ) {
+        throw $exception;
+      }
+      // Update the entity type with the referenced type.
+      $entity_type = $definitions[$field_name]->getSetting('target_type');
+    };
+    // Put the leave field back before imploding.
+    array_push($parts, $leave_field);
+    return implode('.entity.', $parts);
+  }
+
+  /**
    * Adds cacheability metadata to an entity.
    *
    * @param ResourceResponse $response
diff --git a/src/Routing/Routes.php b/src/Routing/Routes.php
index 2da0aaa..e73c760 100644
--- a/src/Routing/Routes.php
+++ b/src/Routing/Routes.php
@@ -24,13 +24,6 @@ class Routes implements ContainerInjectionInterface {
   const FRONT_CONTROLLER = '\Drupal\jsonapi\RequestHandler::handle';
 
   /**
-   * The entity type manager object.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
    * The resource manager interface.
    *
    * @var \Drupal\jsonapi\Configuration\ResourceManagerInterface
@@ -45,8 +38,7 @@ class Routes implements ContainerInjectionInterface {
    * @param \Drupal\jsonapi\Configuration\ResourceManagerInterface $resource_manager
    *   The resource manager.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, ResourceManagerInterface $resource_manager) {
-    $this->entityTypeManager = $entity_type_manager;
+  public function __construct(ResourceManagerInterface $resource_manager) {
     $this->resourceManager = $resource_manager;
   }
 
@@ -54,11 +46,9 @@ class Routes implements ContainerInjectionInterface {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    /* @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
-    $entity_type_manager = $container->get('entity_type.manager');
     /* @var \Drupal\jsonapi\Configuration\ResourceManagerInterface $resource_manager */
     $resource_manager = $container->get('jsonapi.resource.manager');
-    return new static($entity_type_manager, $resource_manager);
+    return new static($resource_manager);
   }
 
   /**
diff --git a/tests/src/Kernel/Resource/EntityResourceTest.php b/tests/src/Kernel/Resource/EntityResourceTest.php
new file mode 100644
index 0000000..7adff5a
--- /dev/null
+++ b/tests/src/Kernel/Resource/EntityResourceTest.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Drupal\Tests\jsonapi\Kernel\Resource;
+
+use Drupal\jsonapi\EntityCollection;
+use Drupal\jsonapi\Resource\DocumentWrapper;
+use Drupal\jsonapi\Resource\EntityResource;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\node\Entity\Node;
+use Drupal\node\Entity\NodeType;
+use Drupal\user\Entity\Role;
+use Drupal\user\Entity\User;
+use Drupal\user\RoleInterface;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class EntityResourceTest.
+ *
+ * @package Drupal\Tests\jsonapi\Kernel\Resource
+ *
+ * @coversDefaultClass \Drupal\jsonapi\Resource\EntityResource
+ * @group jsonapi
+ */
+class EntityResourceTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'node',
+    'jsonapi',
+    'rest',
+    'serialization',
+    'system',
+    'user',
+  ];
+
+  /**
+   * The entity resource under test.
+   *
+   * @var \Drupal\jsonapi\Resource\EntityResource
+   */
+  protected $entityResource;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    // Add the entity schemas.
+    $this->installEntitySchema('node');
+    $this->installEntitySchema('user');
+    // Add the additional table schemas.
+    $this->installSchema('system', ['sequences']);
+    $this->installSchema('node', ['node_access']);
+    $this->installSchema('user', ['users_data']);
+    $type = NodeType::create([
+      'type' => 'article',
+    ]);
+    $type->save();
+    $this->user = User::create([
+      'name' => 'user1',
+      'mail' => 'user@localhost',
+    ]);
+    $this->user->save();
+    $this->node = Node::create([
+      'title' => 'dummy_title',
+      'type' => 'article',
+      'uid' => $this->user->id(),
+    ]);
+
+    $this->node->save();
+
+    // Give anonymous users permission to view user profiles, so that we can
+    // verify the cache tags of cached versions of user profile pages.
+    Role::create([
+      'id' => RoleInterface::ANONYMOUS_ID,
+      'permissions' => [
+        'access user profiles',
+        'access content',
+      ],
+    ])->save();
+
+    $this->entityResource = new EntityResource(
+      $this->container->get('jsonapi.resource.manager')->get('node', 'article'),
+      $this->container->get('entity_type.manager'),
+      $this->container->get('jsonapi.query_builder'),
+      $this->container->get('entity_field.manager')
+    );
+
+  }
+
+
+  /**
+   * @covers ::getIndividual
+   */
+  public function testGetIndividual() {
+    $response = $this->entityResource->getIndividual($this->node);
+    $this->assertInstanceOf(DocumentWrapper::class, $response->getResponseData());
+    $this->assertEquals(1, $response->getResponseData()->getData()->id());
+    $this->assertSame('node:1', $response->getCacheableMetadata()->getCacheTags()[0]);
+  }
+
+  /**
+   * @covers ::getIndividual
+   * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   */
+  public function testGetIndividualDenied() {
+    $role = Role::load(RoleInterface::ANONYMOUS_ID);
+    $role->revokePermission('access content');
+    $role->save();
+    $this->entityResource->getIndividual($this->node);
+  }
+
+  /**
+   * @covers ::getCollection
+   */
+  public function testGetCollection() {
+    // Fake the request.
+    $request = $this->prophesize(Request::class);
+    $params = $this->prophesize(ParameterBag::class);
+    $params->get('_route_params')->willReturn(['_json_api_params' => []]);
+    $request->attributes = $params->reveal();
+
+    // Get the response.
+    $response = $this->entityResource->getCollection($request->reveal());
+
+    // Assertions.
+    $this->assertInstanceOf(DocumentWrapper::class, $response->getResponseData());
+    $this->assertInstanceOf(EntityCollection::class, $response->getResponseData()->getData());
+    $this->assertEquals(1, $response->getResponseData()->getData()->getIterator()->current()->id());
+    $this->assertEquals(['node:1', 'node_list'], $response->getCacheableMetadata()->getCacheTags());
+  }
+
+}
diff --git a/tests/src/Unit/Routing/RoutesTest.php b/tests/src/Unit/Routing/RoutesTest.php
new file mode 100644
index 0000000..f5904aa
--- /dev/null
+++ b/tests/src/Unit/Routing/RoutesTest.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Drupal\Tests\jsonapi\Unit\Routing;
+
+use Drupal\Core\Config\ImmutableConfig;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\jsonapi\Configuration\ResourceConfigInterface;
+use Drupal\jsonapi\Configuration\ResourceManagerInterface;
+use Drupal\jsonapi\Routing\Routes;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class RoutesTest.
+ *
+ * @package Drupal\Tests\jsonapi\Unit\Routing
+ *
+ * @coversDefaultClass \Drupal\jsonapi\Routing\Routes
+ * @group jsonapi
+ */
+class RoutesTest extends UnitTestCase {
+
+  /**
+   * List of routes objects for the different scenarios.
+   *
+   * @var Routes[]
+   */
+  protected $routes;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    // Mock the resource manager to have some resources available.
+    $resource_manager = $this->prophesize(ResourceManagerInterface::class);
+
+    // Create some resource mocks for the manager.
+    $resource_config = $this->prophesize(ResourceConfigInterface::class);
+    $global_config = $this->prophesize(ImmutableConfig::class);
+    $global_config->get('prefix')->willReturn('api');
+    $resource_config->getGlobalConfig()->willReturn($global_config->reveal());
+    $resource_config->getEntityTypeId()->willReturn('entity_type_1');
+    $resource_config->getBundleId()->willReturn('bundle_1_1');
+    // Make sure that we're not coercing the bundle into the path, they can be
+    // different in the future.
+    $resource_config->getPath()->willReturn('/bundle_path_1');
+    $resource_config->getTypeName()->willReturn('resource_type_1');
+    $resource_config->getDeserializationTargetClass()->willReturn('\Drupal\jsonapi\EntityType1');
+    $resource_manager->all()->willReturn([$resource_config->reveal()]);
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $container = $this->prophesize(ContainerInterface::class);
+    $container->get('jsonapi.resource.manager')->willReturn($resource_manager->reveal());
+
+    $this->routes['ok'] = Routes::create($container->reveal());
+  }
+
+
+  /**
+   * @covers ::routes
+   */
+  public function testRoutesCollection() {
+    // Get the route collection and start making assertions.
+    $routes = $this->routes['ok']->routes();
+
+    // Make sure that there are 4 routes for each resource.
+    $this->assertEquals(4, $routes->count());
+
+    $iterator = $routes->getIterator();
+    // Check the collection route.
+    /** @var \Symfony\Component\Routing\Route $route */
+    $route = $iterator->offsetGet('api.dynamic.resource_type_1.collection');
+    $this->assertSame('/api/bundle_path_1', $route->getPath());
+    $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
+    $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
+    $this->assertEquals(['GET', 'POST'], $route->getMethods());
+    $this->assertSame('\Drupal\jsonapi\RequestHandler::handle', $route->getDefault('_controller'));
+    $this->assertSame('\Drupal\jsonapi\EntityType1', $route->getOption('serialization_class'));
+  }
+
+  /**
+   * @covers ::routes
+   */
+  public function testRoutesIndividual() {
+    // Get the route collection and start making assertions.
+    $iterator = $this->routes['ok']->routes()->getIterator();
+
+    // Check the individual route.
+    /** @var \Symfony\Component\Routing\Route $route */
+    $route = $iterator->offsetGet('api.dynamic.resource_type_1.individual');
+    $this->assertSame('/api/bundle_path_1/{entity_type_1}', $route->getPath());
+    $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
+    $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
+    $this->assertEquals(['GET', 'PATCH', 'DELETE'], $route->getMethods());
+    $this->assertSame('\Drupal\jsonapi\RequestHandler::handle', $route->getDefault('_controller'));
+    $this->assertSame('\Drupal\jsonapi\EntityType1', $route->getOption('serialization_class'));
+    $this->assertEquals(['entity_type_1' => ['type' => 'entity:entity_type_1']], $route->getOption('parameters'));
+  }
+
+  /**
+   * @covers ::routes
+   */
+  public function testRoutesRelated() {
+    // Get the route collection and start making assertions.
+    $iterator = $this->routes['ok']->routes()->getIterator();
+
+    // Check the related route.
+    /** @var \Symfony\Component\Routing\Route $route */
+    $route = $iterator->offsetGet('api.dynamic.resource_type_1.related');
+    $this->assertSame('/api/bundle_path_1/{entity_type_1}/{related}', $route->getPath());
+    $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
+    $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
+    $this->assertEquals(['GET'], $route->getMethods());
+    $this->assertSame('\Drupal\jsonapi\RequestHandler::handle', $route->getDefault('_controller'));
+    $this->assertEquals(['entity_type_1' => ['type' => 'entity:entity_type_1']], $route->getOption('parameters'));
+  }
+
+  /**
+   * @covers ::routes
+   */
+  public function testRoutesRelationships() {
+    // Get the route collection and start making assertions.
+    $iterator = $this->routes['ok']->routes()->getIterator();
+
+    // Check the relationships route.
+    /** @var \Symfony\Component\Routing\Route $route */
+    $route = $iterator->offsetGet('api.dynamic.resource_type_1.relationship');
+    $this->assertSame('/api/bundle_path_1/{entity_type_1}/relationships/{related}', $route->getPath());
+    $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
+    $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
+    $this->assertEquals(['GET', 'POST', 'DELETE'], $route->getMethods());
+    $this->assertSame('\Drupal\jsonapi\RequestHandler::handle', $route->getDefault('_controller'));
+    $this->assertEquals(['entity_type_1' => ['type' => 'entity:entity_type_1']], $route->getOption('parameters'));
+  }
+
+}
