diff --git a/src/Controller/EntityResource.php b/src/Controller/EntityResource.php
index 294c03f..4b4a7da 100644
--- a/src/Controller/EntityResource.php
+++ b/src/Controller/EntityResource.php
@@ -31,7 +31,6 @@ use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
-use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
@@ -362,7 +361,6 @@ class EntityResource {
     $related_field = $this->resourceType->getInternalName($related_field);
     /* @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $field_list */
     $field_list = $entity->get($related_field);
-    $this->validateReferencedResource($field_list, $related_field);
     // Add the cacheable metadata from the host entity.
     $cacheable_metadata = CacheableMetadata::createFromObject($entity);
     $is_multiple = $field_list
@@ -383,10 +381,10 @@ class EntityResource {
     $referenced_entities = array_filter(
       $field_list->referencedEntities(),
       function (EntityInterface $entity) {
-        return (bool) $this->resourceTypeRepository->get(
+        return ($resource_type = $this->resourceTypeRepository->get(
           $entity->getEntityTypeId(),
           $entity->bundle()
-        );
+        )) && !$resource_type->isInternal();
       }
     );
     foreach ($referenced_entities as $referenced_entity) {
@@ -425,60 +423,10 @@ class EntityResource {
   public function getRelationship(EntityInterface $entity, $related_field, Request $request, $response_code = 200) {
     $related_field = $this->resourceType->getInternalName($related_field);
     $field_list = $entity->get($related_field);
-    $this->validateReferencedResource($field_list, $related_field);
     $response = $this->buildWrappedResponse($field_list, $response_code);
     return $response;
   }
 
-  /**
-   * Validates that the referenced field points to an enabled resource.
-   *
-   * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface|null $field_list
-   *   The field list with the reference.
-   * @param string $related_field
-   *   The internal name of the related field.
-   *
-   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
-   *   If the field is not a reference or the target resource is disabled.
-   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
-   *   If the $field_list is of the incorrect type.
-   */
-  protected function validateReferencedResource($field_list, $related_field) {
-    if (
-      !is_null($field_list) &&
-      !$field_list instanceof EntityReferenceFieldItemListInterface
-    ) {
-      throw new HttpException(500, 'Invalid internal structure for relationship field list.');
-    }
-    if (!$field_list || !$this->isRelationshipField($field_list)) {
-      throw new NotFoundHttpException(sprintf('The relationship %s is not present in this resource.', $related_field));
-    }
-    // Make sure we return a 404 if the related resource is disabled for
-    // cardinality 1.
-    $is_multiple = $field_list
-      ->getDataDefinition()
-      ->getFieldStorageDefinition()
-      ->isMultiple();
-    // For multiple resource the normalizer will skip the disabled items.
-    if (!$is_multiple) {
-      /** @var \Drupal\Core\Entity\EntityInterface $referenced_entity */
-      $referenced_entity = $field_list->entity;
-      $referenced_resource_type = $this->resourceTypeRepository
-        ->get(
-          $referenced_entity->getEntityTypeId(),
-          $referenced_entity->bundle()
-        );
-      if (!$referenced_resource_type) {
-        throw new NotFoundHttpException(sprintf(
-          'Unable to find the resource for "%s:%s" on the related field "%s".',
-          $referenced_entity->getEntityTypeId(),
-          $referenced_entity->bundle(),
-          $related_field
-        ));
-      }
-    }
-  }
-
   /**
    * Adds a relationship to a to-many relationship.
    *
diff --git a/src/Controller/EntryPoint.php b/src/Controller/EntryPoint.php
index bc942f6..b685ec9 100644
--- a/src/Controller/EntryPoint.php
+++ b/src/Controller/EntryPoint.php
@@ -70,7 +70,12 @@ class EntryPoint extends ControllerBase {
     $do_build_urls = function () {
       $self = Url::fromRoute('jsonapi.resource_list')->setAbsolute();
 
-      return array_reduce($this->resourceTypeRepository->all(), function (array $carry, ResourceType $resource_type) {
+      // Only build URLs for exposed resources.
+      $resources = array_filter($this->resourceTypeRepository->all(), function ($resource) {
+        return !$resource->isInternal();
+      });
+
+      return array_reduce($resources, function (array $carry, ResourceType $resource_type) {
         // TODO: Learn how to invalidate the cache for this page when a new
         // entity type or bundle gets added, removed or updated.
         // $this->response->addCacheableDependency($definition);
diff --git a/src/Controller/RequestHandler.php b/src/Controller/RequestHandler.php
index d0e301a..50e49be 100644
--- a/src/Controller/RequestHandler.php
+++ b/src/Controller/RequestHandler.php
@@ -41,11 +41,13 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    *   The route match.
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   The HTTP request object.
+   * @param string $relationship_field
+   *   (optional) The public relationship field name.
    *
    * @return \Drupal\Core\Cache\CacheableResponseInterface
    *   The response object.
    */
-  public function handle(RouteMatchInterface $route_match, Request $request) {
+  public function handle(RouteMatchInterface $route_match, Request $request, $relationship_field = NULL) {
     $method = strtolower($request->getMethod());
     $route = $route_match->getRouteObject();
 
@@ -62,14 +64,16 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
 
     // Determine the request parameters that should be passed to the resource
     // plugin.
-    $route_parameters = $route_match->getParameters();
     $parameters = [];
 
-    // Filter out all internal parameters starting with "_".
-    foreach ($route_parameters as $key => $parameter) {
-      if ($key{0} !== '_') {
-        $parameters[] = $parameter;
-      }
+    $entity_type_id = $route->getRequirement('_entity_type');
+
+    if ($entity = $route_match->getParameter($entity_type_id)) {
+      $parameters[$entity_type_id] = $entity;
+    }
+
+    if ($relationship_field) {
+      $parameters[] = $relationship_field;
     }
 
     // Invoke the operation on the resource plugin.
@@ -116,7 +120,7 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
       return NULL;
     }
     $resource_type = $current_context->getResourceType();
-    $field_related = $resource_type->getInternalName($request->get('related'));
+    $field_related = $resource_type->getInternalName($request->get('relationship_field'));
     try {
       return $serializer->deserialize($received, $serialization_class, 'api_json', [
         'related' => $field_related,
@@ -151,7 +155,7 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
         if ($on_relationship) {
           return 'getRelationship';
         }
-        elseif ($route_match->getParameter('related')) {
+        elseif ($route_match->getParameter('relationship_field')) {
           return 'getRelated';
         }
         return $this->getEntity($route_match) ? 'getIndividual' : 'getCollection';
diff --git a/src/LinkManager/LinkManager.php b/src/LinkManager/LinkManager.php
index 2128644..1b05c26 100644
--- a/src/LinkManager/LinkManager.php
+++ b/src/LinkManager/LinkManager.php
@@ -56,15 +56,26 @@ class LinkManager {
    *   Parameters for the route generation.
    * @param string $key
    *   A key to build the route identifier.
+   * @param string $relationship_field
+   *   The public relationship field name.
    *
    * @return string
    *   The URL string.
    */
-  public function getEntityLink($entity_id, ResourceType $resource_type, array $route_parameters, $key) {
+  public function getEntityLink($entity_id, ResourceType $resource_type, array $route_parameters, $key, $relationship_field = NULL) {
     $route_parameters += [
       $resource_type->getEntityTypeId() => $entity_id,
     ];
     $route_key = sprintf('jsonapi.%s.%s', $resource_type->getTypeName(), $key);
+    if (in_array($key, ['related', 'relationship'])) {
+      // Routes are generated per field and no longer need the field name as a
+      // route parameter. However, we need to fall back to the 'related' route
+      // parameter to preserve backwards compatibility.
+      $relationship = ($relationship_field) ?
+        $relationship_field :
+        $route_parameters['related'];
+      $route_key .= ".{$relationship}";
+    }
     return $this->urlGenerator->generateFromRoute($route_key, $route_parameters, ['absolute' => TRUE]);
   }
 
diff --git a/src/Normalizer/Value/RelationshipNormalizerValue.php b/src/Normalizer/Value/RelationshipNormalizerValue.php
index df18b82..04b0223 100644
--- a/src/Normalizer/Value/RelationshipNormalizerValue.php
+++ b/src/Normalizer/Value/RelationshipNormalizerValue.php
@@ -70,25 +70,24 @@ class RelationshipNormalizerValue extends FieldNormalizerValue {
       // an empty array.
       return $this->cardinality == 1 ? ['data' => NULL] : ['data' => []];
     }
+    $relationship_field = $this->resourceType->getPublicName($this->fieldName);
     // Generate the links for the relationship.
-    $route_parameters = [
-      // Make sure to point to the public facing fields.
-      'related' => $this->resourceType->getPublicName($this->fieldName),
-    ];
     return [
       'data' => $value,
       'links' => [
         'self' => $this->linkManager->getEntityLink(
           $this->hostEntityId,
           $this->resourceType,
-          $route_parameters,
-          'relationship'
+          [],
+          'relationship',
+          $relationship_field
         ),
         'related' => $this->linkManager->getEntityLink(
           $this->hostEntityId,
           $this->resourceType,
-          $route_parameters,
-          'related'
+          [],
+          'related',
+          $relationship_field
         ),
       ],
     ];
diff --git a/src/ResourceType/ResourceTypeRepository.php b/src/ResourceType/ResourceTypeRepository.php
index 34b20af..3ca5804 100644
--- a/src/ResourceType/ResourceTypeRepository.php
+++ b/src/ResourceType/ResourceTypeRepository.php
@@ -142,11 +142,13 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface {
         $resource_type->getBundle()
       );
 
-      return array_map(function ($field_definition) {
+      return array_filter(array_map(function ($field_definition) {
         return $this->getRelatableResourceTypesFromFieldDefinition($field_definition);
       }, array_filter($field_definitions, function ($field_definition) {
         return $this->isReferenceFieldDefinition($field_definition);
-      }));
+      })), function ($resource_types) {
+        return !empty($resource_types);
+      });
     }
     return [];
   }
diff --git a/src/Routing/Routes.php b/src/Routing/Routes.php
index db22c8f..e2511ec 100644
--- a/src/Routing/Routes.php
+++ b/src/Routing/Routes.php
@@ -5,6 +5,7 @@ namespace Drupal\jsonapi\Routing;
 use Drupal\Core\Authentication\AuthenticationCollectorInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Field\EntityReferenceFieldItemList;
+use Drupal\jsonapi\ResourceType\ResourceType;
 use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface;
 use Drupal\jsonapi\Resource\JsonApiDocumentTopLevel;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -99,10 +100,19 @@ class Routes implements ContainerInjectionInterface {
    */
   public function routes() {
     $collection = new RouteCollection();
-    foreach ($this->resourceTypeRepository->all() as $resource_type) {
+
+    // Do not create any routes for internal resources.
+    /* @var \Drupal\jsonapi\ResourceType\ResourceType[] $resources */
+    $resources = array_filter($this->resourceTypeRepository->all(), function ($resource) {
+      return !$resource->isInternal();
+    });
+
+    foreach ($resources as $resource_type) {
       $route_base_path = sprintf('/jsonapi/%s/%s', $resource_type->getEntityTypeId(), $resource_type->getBundle());
-      $build_route_name = function ($key) use ($resource_type) {
-        return sprintf('jsonapi.%s.%s', $resource_type->getTypeName(), $key);
+      $build_route_name = function ($key, $field = NULL) use ($resource_type) {
+        $route_name = sprintf('jsonapi.%s.%s', $resource_type->getTypeName(), $key);
+        $route_name .= ($field) ? ".{$field}" : '';
+        return $route_name;
       };
 
       $defaults = [
@@ -140,30 +150,39 @@ class Routes implements ContainerInjectionInterface {
       $route_individual->addOptions($options);
       $collection->add($build_route_name('individual'), $route_individual);
 
-      // Related resource, like /jsonapi/file/photo/123/comments.
-      $route_related = (new Route(sprintf('%s/{%s}/{related}', $route_base_path, $resource_type->getEntityTypeId()), $defaults))
-        ->setRequirement('_entity_type', (string) $resource_type->getEntityTypeId())
-        ->setRequirement('_bundle', (string) $resource_type->getBundle())
-        ->setRequirement('_permission', 'access content')
-        ->setRequirement('_jsonapi_custom_query_parameter_names', 'TRUE')
-        ->setOption('parameters', $parameters)
-        ->setOption('_auth', $this->authProviderList())
-        ->setMethods(['GET']);
-      $route_related->addOptions($options);
-      $collection->add($build_route_name('related'), $route_related);
-
-      // Related endpoint, like /jsonapi/file/photo/123/relationships/comments.
-      $route_relationship = (new Route(sprintf('%s/{%s}/relationships/{related}', $route_base_path, $resource_type->getEntityTypeId()), $defaults + ['_on_relationship' => TRUE]))
-        ->setRequirement('_entity_type', (string) $resource_type->getEntityTypeId())
-        ->setRequirement('_bundle', (string) $resource_type->getBundle())
-        ->setRequirement('_permission', 'access content')
-        ->setRequirement('_jsonapi_custom_query_parameter_names', 'TRUE')
-        ->setOption('parameters', $parameters)
-        ->setOption('_auth', $this->authProviderList())
-        ->setOption('serialization_class', EntityReferenceFieldItemList::class)
-        ->setMethods(['GET', 'POST', 'PATCH', 'DELETE']);
-      $route_relationship->addOptions($options);
-      $collection->add($build_route_name('relationship'), $route_relationship);
+      $relationships = $this->getRoutableRelationships($resource_type);
+      foreach ($relationships as $field => $relatable_resource_types) {
+        if (static::hasExternalResourceTypes($relatable_resource_types)) {
+          // Related resource like /jsonapi/file/photo/123/comments.
+          $path = sprintf('%s/{%s}/%s', $route_base_path, $resource_type->getEntityTypeId(), $field);
+          $route_related = (new Route($path, $defaults))
+            ->addDefaults(['relationship_field' => $field])
+            ->setRequirement('_entity_type', (string) $resource_type->getEntityTypeId())
+            ->setRequirement('_bundle', (string) $resource_type->getBundle())
+            ->setRequirement('_permission', 'access content')
+            ->setRequirement('_jsonapi_custom_query_parameter_names', 'TRUE')
+            ->setOption('parameters', $parameters)
+            ->setOption('_auth', $this->authProviderList())
+            ->setMethods(['GET']);
+          $route_related->addOptions($options);
+          $collection->add($build_route_name('related', $field), $route_related);
+        }
+
+        // Related endpoint like /jsonapi/file/photo/123/relationships/comments.
+        $path = sprintf('%s/{%s}/relationships/%s', $route_base_path, $resource_type->getEntityTypeId(), $field);
+        $route_relationship = (new Route($path, $defaults + ['_on_relationship' => TRUE]))
+          ->addDefaults(['relationship_field' => $field])
+          ->setRequirement('_entity_type', (string) $resource_type->getEntityTypeId())
+          ->setRequirement('_bundle', (string) $resource_type->getBundle())
+          ->setRequirement('_permission', 'access content')
+          ->setRequirement('_jsonapi_custom_query_parameter_names', 'TRUE')
+          ->setOption('parameters', $parameters)
+          ->setOption('_auth', $this->authProviderList())
+          ->setOption('serialization_class', EntityReferenceFieldItemList::class)
+          ->setMethods(['GET', 'POST', 'PATCH', 'DELETE']);
+        $route_relationship->addOptions($options);
+        $collection->add($build_route_name('relationship', $field), $route_relationship);
+      }
     }
 
     return $collection;
@@ -184,4 +203,40 @@ class Routes implements ContainerInjectionInterface {
     return $this->providerIds;
   }
 
+  /**
+   * Identifies fields and resource types for which routes might be generated.
+   *
+   * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
+   *   The base resource type for which to get routabale relationships.
+   *
+   * @return array
+   *   An array of relatable resource types keyed by enabled relationship
+   *   fields.
+   */
+  protected static function getRoutableRelationships(ResourceType $resource_type) {
+    return array_filter($resource_type->getRelatableResourceTypes(), function ($field) use ($resource_type) {
+      return $resource_type->isFieldEnabled($field);
+    }, ARRAY_FILTER_USE_KEY);
+  }
+
+  /**
+   * Determines if a relationship has any external resource types.
+   *
+   * This may be used to determine is a related route should be generated.
+   *
+   * @param \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types
+   *   An array of JSON API resource types to check.
+   *
+   * @return bool
+   *   TRUE if none of the given resource types are external, FALSE otherwise.
+   */
+  protected static function hasExternalResourceTypes(array $resource_types) {
+    foreach ($resource_types as $resource_type) {
+      if (!$resource_type->isInternal()) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
 }
diff --git a/tests/src/Functional/JsonApiFunctionalTest.php b/tests/src/Functional/JsonApiFunctionalTest.php
index 61b1cea..81a9596 100644
--- a/tests/src/Functional/JsonApiFunctionalTest.php
+++ b/tests/src/Functional/JsonApiFunctionalTest.php
@@ -699,9 +699,8 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
         ],
       ],
     ];
-    $relationship_url = Url::fromRoute('jsonapi.node--article.relationship', [
+    $relationship_url = Url::fromRoute('jsonapi.node--article.relationship.field_tags', [
       'node' => $uuid,
-      'related' => 'field_tags',
     ]);
     $response = $this->request('POST', $relationship_url, [
       'body' => Json::encode($body),
diff --git a/tests/src/Kernel/Controller/EntryPointTest.php b/tests/src/Kernel/Controller/EntryPointTest.php
index 25ebd83..7e71bff 100644
--- a/tests/src/Kernel/Controller/EntryPointTest.php
+++ b/tests/src/Kernel/Controller/EntryPointTest.php
@@ -4,6 +4,8 @@ namespace Drupal\Tests\jsonapi\Kernel\Controller;
 
 use Drupal\Core\Cache\CacheableJsonResponse;
 use Drupal\jsonapi\Controller\EntryPoint;
+use Drupal\jsonapi\ResourceType\ResourceType;
+use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface;
 use Drupal\Tests\jsonapi\Kernel\JsonapiKernelTestBase;
 
 /**
@@ -27,24 +29,72 @@ class EntryPointTest extends JsonapiKernelTestBase {
   ];
 
   /**
-   * @covers ::index
+   * The subject under test.
+   *
+   * @var \Drupal\jsonapi\Controller\EntryPoint
+   */
+  protected $entryPoint;
+
+  /**
+   * {@inheritdoc}
    */
-  public function testIndex() {
-    $controller = new EntryPoint(
-      \Drupal::service('jsonapi.resource_type.repository'),
+  public function setUp() {
+    parent::setUp();
+    $resource_repository = $this->prophesize(ResourceTypeRepositoryInterface::class);
+    $resource_repository->all()->willReturn([
+      new ResourceType('user', 'user', NULL),
+      new ResourceType('node_type', 'node_type', NULL),
+      new ResourceType('menu', 'menu', NULL, TRUE),
+    ]);
+    $this->entryPoint = new EntryPoint(
+      $resource_repository->reveal(),
       \Drupal::service('renderer'),
       new CacheableJsonResponse()
     );
-    $processed_response = $controller->index();
-    $this->assertEquals(
+  }
+
+  /**
+   * @covers ::index
+   *
+   * @param string $path
+   *   The regular expression which should validate the link.
+   * @param string $link
+   *   The link key to test.
+   *
+   * @dataProvider indexProvider
+   */
+  public function testIndex($path, $link) {
+    $data = json_decode($this->entryPoint->index()->getContent(), TRUE);
+    $this->assertRegExp($path, $data['links'][$link]);
+  }
+
+  /**
+   * Provides test cases to test entry point route generation.
+   */
+  public function indexProvider() {
+    return [
+      'root' => ['path' => '/.*\/jsonapi/', 'link' => 'self'],
+      'user' => ['path' => '/.*\/jsonapi\/user\/user/', 'link' => 'user--user'],
+      'node_type' => ['path' => '/.*\/jsonapi\/node_type\/node_type/', 'link' => 'node_type--node_type'],
+    ];
+  }
+
+  /**
+   * Ensures that internal resources are not present in the entry point.
+   */
+  public function testInternalResource() {
+    $data = json_decode($this->entryPoint->index()->getContent(), TRUE);
+    $this->assertArrayNotHasKey('menu--menu', $data['links']);
+  }
+
+  /**
+   * Ensures that the site name cache context was added.
+   */
+  public function testCacheContext() {
+    $this->assertArraySubset(
       ['url.site'],
-      $processed_response->getCacheableMetadata()->getCacheContexts()
+      $this->entryPoint->index()->getCacheableMetadata()->getCacheContexts()
     );
-    $data = json_decode($processed_response->getContent(), TRUE);
-    $links = $data['links'];
-    $this->assertRegExp('/.*\/jsonapi/', $links['self']);
-    $this->assertRegExp('/.*\/jsonapi\/user\/user/', $links['user--user']);
-    $this->assertRegExp('/.*\/jsonapi\/node_type\/node_type/', $links['node_type--node_type']);
   }
 
 }
diff --git a/tests/src/Kernel/EntityToJsonApiTest.php b/tests/src/Kernel/EntityToJsonApiTest.php
index 6bc0260..82ba6b9 100644
--- a/tests/src/Kernel/EntityToJsonApiTest.php
+++ b/tests/src/Kernel/EntityToJsonApiTest.php
@@ -139,7 +139,7 @@ class EntityToJsonApiTest extends JsonapiKernelTestBase {
 
     $link_manager = $this->prophesize(LinkManager::class);
     $link_manager
-      ->getEntityLink(Argument::any(), Argument::any(), Argument::type('array'), Argument::type('string'))
+      ->getEntityLink(Argument::any(), Argument::any(), Argument::type('array'), Argument::type('string'), Argument::cetera())
       ->willReturn('dummy_entity_link');
     $link_manager
       ->getRequestLink(Argument::any())
diff --git a/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php b/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php
index ce06be1..c4e25ec 100644
--- a/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php
+++ b/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php
@@ -155,7 +155,7 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase {
 
     $link_manager = $this->prophesize(LinkManager::class);
     $link_manager
-      ->getEntityLink(Argument::any(), Argument::any(), Argument::type('array'), Argument::type('string'))
+      ->getEntityLink(Argument::any(), Argument::any(), Argument::type('array'), Argument::type('string'), Argument::cetera())
       ->willReturn('dummy_entity_link');
     $link_manager
       ->getRequestLink(Argument::any())
diff --git a/tests/src/Unit/Normalizer/Value/RelationshipNormalizerValueTest.php b/tests/src/Unit/Normalizer/Value/RelationshipNormalizerValueTest.php
index e66e9c3..3f7c2b2 100644
--- a/tests/src/Unit/Normalizer/Value/RelationshipNormalizerValueTest.php
+++ b/tests/src/Unit/Normalizer/Value/RelationshipNormalizerValueTest.php
@@ -25,7 +25,7 @@ class RelationshipNormalizerValueTest extends UnitTestCase {
   public function testRasterizeValue($values, $cardinality, $expected) {
     $link_manager = $this->prophesize(LinkManager::class);
     $link_manager
-      ->getEntityLink(Argument::any(), Argument::any(), Argument::type('array'), Argument::type('string'))
+      ->getEntityLink(Argument::any(), Argument::any(), Argument::type('array'), Argument::type('string'), Argument::type('string'))
       ->willReturn('dummy_entity_link');
     $object = new RelationshipNormalizerValue($values, $cardinality, [
       'link_manager' => $link_manager->reveal(),
diff --git a/tests/src/Unit/Routing/RoutesTest.php b/tests/src/Unit/Routing/RoutesTest.php
index f6b2899..e2584d4 100644
--- a/tests/src/Unit/Routing/RoutesTest.php
+++ b/tests/src/Unit/Routing/RoutesTest.php
@@ -31,11 +31,22 @@ class RoutesTest extends UnitTestCase {
    */
   protected function setUp() {
     parent::setUp();
+    $type_1 = new ResourceType('entity_type_1', 'bundle_1_1', EntityInterface::class);
+    $type_2 = new ResourceType('entity_type_2', 'bundle_2_1', EntityInterface::class, TRUE);
+    $relatable_resource_types = [
+      'external' => [$type_1],
+      'internal' => [$type_2],
+      'both' => [$type_1, $type_2],
+    ];
+    $type_1->setRelatableResourceTypes($relatable_resource_types);
+    $type_2->setRelatableResourceTypes($relatable_resource_types);
+    // This type ensures that we can create routes for bundle IDs which might be
+    // cast from strings to integers.  It should not affect related resource
+    // routing.
+    $type_3 = new ResourceType('entity_type_3', '123', EntityInterface::class, TRUE);
+    $type_3->setRelatableResourceTypes([]);
     $resource_type_repository = $this->prophesize(ResourceTypeRepository::class);
-    $resource_type_repository->all()->willReturn([
-      new ResourceType('entity_type_1', 'bundle_1_1', EntityInterface::class),
-      new ResourceType('entity_type_2', 123, EntityInterface::class),
-    ]);
+    $resource_type_repository->all()->willReturn([$type_1, $type_2, $type_3]);
     $container = $this->prophesize(ContainerInterface::class);
     $container->get('jsonapi.resource_type.repository')->willReturn($resource_type_repository->reveal());
     $auth_collector = $this->prophesize(AuthenticationCollectorInterface::class);
@@ -55,9 +66,6 @@ class RoutesTest extends UnitTestCase {
     // Get the route collection and start making assertions.
     $routes = $this->routes['ok']->routes();
 
-    // Make sure that there are 4 routes for each resource (2*4).
-    $this->assertEquals(8, $routes->count());
-
     $iterator = $routes->getIterator();
     // Check the collection route.
     /** @var \Symfony\Component\Routing\Route $route */
@@ -100,8 +108,8 @@ class RoutesTest extends UnitTestCase {
 
     // Check the related route.
     /** @var \Symfony\Component\Routing\Route $route */
-    $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.related');
-    $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity_type_1}/{related}', $route->getPath());
+    $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.related.external');
+    $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity_type_1}/external', $route->getPath());
     $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
     $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
     $this->assertEquals(['GET'], $route->getMethods());
@@ -119,8 +127,8 @@ class RoutesTest extends UnitTestCase {
 
     // Check the relationships route.
     /** @var \Symfony\Component\Routing\Route $route */
-    $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.relationship');
-    $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity_type_1}/relationships/{related}', $route->getPath());
+    $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.relationship.internal');
+    $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity_type_1}/relationships/internal', $route->getPath());
     $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
     $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
     $this->assertEquals(['GET', 'POST', 'PATCH', 'DELETE'], $route->getMethods());
@@ -130,4 +138,57 @@ class RoutesTest extends UnitTestCase {
     $this->assertSame('Drupal\Core\Field\EntityReferenceFieldItemList', $route->getOption('serialization_class'));
   }
 
+  /**
+   * Ensures that the expected routes are created or not created.
+   *
+   * @dataProvider expectedRoutes
+   */
+  public function testRoutes($route) {
+    $this->assertArrayHasKey($route, $this->routes['ok']->routes()->all());
+  }
+
+  /**
+   * Lists routes which should have been created.
+   */
+  public function expectedRoutes() {
+    return [
+      ['jsonapi.entity_type_1--bundle_1_1.individual'],
+      ['jsonapi.entity_type_1--bundle_1_1.collection'],
+      ['jsonapi.entity_type_1--bundle_1_1.related.external'],
+      ['jsonapi.entity_type_1--bundle_1_1.related.both'],
+      ['jsonapi.entity_type_1--bundle_1_1.relationship.internal'],
+      ['jsonapi.entity_type_1--bundle_1_1.relationship.external'],
+      ['jsonapi.entity_type_1--bundle_1_1.relationship.both'],
+    ];
+  }
+
+  /**
+   * Ensures that no routes are created for internal resources.
+   *
+   * @dataProvider notExpectedRoutes
+   */
+  public function testInternalRoutes($route) {
+    $this->assertArrayNotHasKey($route, $this->routes['ok']->routes()->all());
+  }
+
+  /**
+   * Lists routes which should have been created.
+   */
+  public function notExpectedRoutes() {
+    return [
+      ['jsonapi.entity_type_1--bundle_1_1.related.none'],
+      ['jsonapi.entity_type_1--bundle_1_1.related.internal'],
+      ['jsonapi.entity_type_1--bundle_1_1.relationship.none'],
+
+      ['jsonapi.entity_type_2--bundle_2_1.individual'],
+      ['jsonapi.entity_type_2--bundle_2_1.collection'],
+      ['jsonapi.entity_type_2--bundle_2_1.related.both'],
+      ['jsonapi.entity_type_2--bundle_2_1.related.external'],
+      ['jsonapi.entity_type_2--bundle_2_1.relationship.none'],
+      ['jsonapi.entity_type_2--bundle_2_1.relationship.internal'],
+      ['jsonapi.entity_type_2--bundle_2_1.relationship.external'],
+      ['jsonapi.entity_type_2--bundle_2_1.relationship.both'],
+    ];
+  }
+
 }
