diff --git a/src/Controller/EntityResource.php b/src/Controller/EntityResource.php
index 4b24048..72a5a32 100644
--- a/src/Controller/EntityResource.php
+++ b/src/Controller/EntityResource.php
@@ -13,7 +13,9 @@ use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
+use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Field\Plugin\DataType\FieldItem;
 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 use Drupal\jsonapi\Context\CurrentContext;
 use Drupal\jsonapi\Exception\EntityAccessDeniedHttpException;
@@ -453,30 +455,6 @@ class EntityResource {
     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
-        ));
-      }
-    }
   }
 
   /**
@@ -842,15 +820,20 @@ class EntityResource {
   /**
    * Checks if is a relationship field.
    *
-   * @param object $entity_field
-   *   Entity definition.
+   * @param \Drupal\Core\Field\FieldItemListInterface $entity_field
+   *   Entity field.
    *
    * @return bool
-   *   Returns TRUE, if entity field is EntityReferenceItem.
+   *   Returns TRUE if entity field is a relationship field with non-internal
+   *   target resource types, FALSE otherwise.
    */
-  protected function isRelationshipField($entity_field) {
-    $class = $this->pluginManager->getPluginClass($entity_field->getDataDefinition()->getType());
-    return ($class == EntityReferenceItem::class || is_subclass_of($class, EntityReferenceItem::class));
+  protected function isRelationshipField(FieldItemListInterface $entity_field) {
+    $resource_types = $this->resourceType->getRelatableResourceTypesByField(
+      $this->resourceType->getInternalName($entity_field->getName())
+    );
+    return !empty($resource_types) && array_reduce($resource_types, function ($has_external, $resource_type) {
+      return $has_external ? TRUE : !$resource_type->isInternal();
+    }, FALSE);
   }
 
   /**
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/Routing/Routes.php b/src/Routing/Routes.php
index c9b4c91..0d4de22 100644
--- a/src/Routing/Routes.php
+++ b/src/Routing/Routes.php
@@ -100,6 +100,10 @@ class Routes implements ContainerInjectionInterface {
   public function routes() {
     $collection = new RouteCollection();
     foreach ($this->resourceTypeRepository->all() as $resource_type) {
+      if ($resource_type->isInternal()) {
+        continue;
+      }
+
       $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);
diff --git a/tests/src/Functional/InternalEntitiesTest.php b/tests/src/Functional/InternalEntitiesTest.php
index 9a0ac98..0bd9869 100644
--- a/tests/src/Functional/InternalEntitiesTest.php
+++ b/tests/src/Functional/InternalEntitiesTest.php
@@ -38,6 +38,13 @@ class InternalEntitiesTest extends BrowserTestBase {
    */
   protected $testUser;
 
+  /**
+   * An entity of an internal entity type.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $internalEntity;
+
   /**
    * An entity referencing an internal entity.
    *
@@ -66,16 +73,67 @@ class InternalEntitiesTest extends BrowserTestBase {
       'Internal Entities',
       'jsonapi_entity_test_no_label'
     );
-    $internal_entity = EntityTestNoLabel::create([]);
-    $internal_entity->save();
+    $this->internalEntity = EntityTestNoLabel::create([]);
+    $this->internalEntity->save();
     $this->referencingEntity = EntityTestWithBundle::create([
       'type' => 'internal_referencer',
-      'field_internal' => $internal_entity->id(),
+      'field_internal' => $this->internalEntity->id(),
     ]);
     $this->referencingEntity->save();
     drupal_flush_all_caches();
   }
 
+  /**
+   * Ensures that internal resources types aren't present in the entry point.
+   */
+  public function testEntryPoint() {
+    if (!method_exists(EntityTypeInterface::class, 'isInternal')) {
+      $this->markTestSkipped('The Drupal Core version must be >= 8.5');
+      return;
+    }
+    $this->drupalLogin($this->testUser);
+    $response = $this->drupalGet('/jsonapi', [], ['Accept' => 'application/vnd.api+json']);
+    $decoded = Json::decode($response);
+    $this->assertArrayNotHasKey(
+      "{$this->internalEntity->getEntityTypeId()}--{$this->internalEntity->bundle()}",
+      $decoded['links'],
+      'The entry point should not contain links to internal resource type routes.'
+    );
+  }
+
+  /**
+   * Ensures that internal resources types aren't present in the routes.
+   */
+  public function testRoutes() {
+    if (!method_exists(EntityTypeInterface::class, 'isInternal')) {
+      $this->markTestSkipped('The Drupal Core version must be >= 8.5');
+      return;
+    }
+    $this->drupalLogin($this->testUser);
+    $internal_entity_type_id = $this->internalEntity->getEntityTypeId();
+    $internal_bundle = $this->internalEntity->bundle();
+    $internal_uuid = $this->internalEntity->uuid();
+    $referencing_entity_type_id = $this->referencingEntity->getEntityTypeId();
+    $referencing_bundle = $this->referencingEntity->bundle();
+    $referencing_uuid = $this->referencingEntity->uuid();
+    // This cannot be in a data provider because it needs values created by the
+    // setUp method.
+    $paths = [
+      'individual' => "/jsonapi/{$internal_entity_type_id}/{$internal_bundle}/{$internal_uuid}",
+      'collection' => "/jsonapi/{$internal_entity_type_id}/{$internal_bundle}",
+      'related' => "/jsonapi/{$referencing_entity_type_id}/{$referencing_bundle}/{$referencing_uuid}/field_internal",
+    ];
+    foreach ($paths as $type => $path) {
+      $response = $this->drupalGet($path, [], ['Accept' => 'application/vnd.api+json']);
+      $decoded = Json::decode($response);
+      $this->assertSame(
+        404,
+        $decoded['errors'][0]['status'],
+        "The '{$type}' route ({$path}) should not be available for internal resource types.'"
+      );
+    }
+  }
+
   /**
    * Asserts that internal entities are not included in compound documents.
    */
@@ -88,7 +146,7 @@ class InternalEntitiesTest extends BrowserTestBase {
     $entity_type_id = $this->referencingEntity->getEntityTypeId();
     $bundle = $this->referencingEntity->bundle();
     $path = "/jsonapi/{$entity_type_id}/{$bundle}/{$this->referencingEntity->uuid()}?include=field_internal";
-    $response = $this->drupalGet($path);
+    $response = $this->drupalGet($path, [], ['Accept' => 'application/vnd.api+json']);
     $decoded = Json::decode($response);
     $this->assertArrayNotHasKey(
       'included',
diff --git a/tests/src/Kernel/Controller/EntityResourceTest.php b/tests/src/Kernel/Controller/EntityResourceTest.php
index c5cdc1f..946853c 100644
--- a/tests/src/Kernel/Controller/EntityResourceTest.php
+++ b/tests/src/Kernel/Controller/EntityResourceTest.php
@@ -26,6 +26,7 @@ use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
 use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -403,7 +404,11 @@ class EntityResourceTest extends JsonapiKernelTestBase {
    */
   public function testGetRelated() {
     // to-one relationship.
-    $entity_resource = $this->buildEntityResource('node', 'article');
+    $entity_resource = $this->buildEntityResource('node', 'article', [
+      'uid' => [new ResourceType('user', 'user', NULL)],
+      'roles' => [new ResourceType('user_role', 'user_role', NULL)],
+      'field_relationships' => [new ResourceType('node', 'article', NULL)],
+    ]);
     $response = $entity_resource->getRelated($this->node, 'uid', new Request());
     $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData());
     $this->assertInstanceOf(User::class, $response->getResponseData()
@@ -445,7 +450,9 @@ class EntityResourceTest extends JsonapiKernelTestBase {
    */
   public function testGetRelationship() {
     // to-one relationship.
-    $entity_resource = $this->buildEntityResource('node', 'article');
+    $entity_resource = $this->buildEntityResource('node', 'article', [
+      'uid' => [new ResourceType('user', 'user', NULL)],
+    ]);
     $response = $entity_resource->getRelationship($this->node, 'uid', new Request());
     $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData());
     $this->assertInstanceOf(
@@ -744,7 +751,9 @@ class EntityResourceTest extends JsonapiKernelTestBase {
       ->grantPermission('edit any article content')
       ->save();
 
-    $entity_resource = $this->buildEntityResource('node', 'article');
+    $entity_resource = $this->buildEntityResource('node', 'article', [
+      'field_relationships' => [new ResourceType('node', 'article', NULL)],
+    ]);
     $response = $entity_resource->createRelationship($this->node, 'field_relationships', $parsed_field_list, new Request());
 
     // As a side effect, the node will also be saved.
@@ -771,7 +780,9 @@ class EntityResourceTest extends JsonapiKernelTestBase {
       ->grantPermission('edit any article content')
       ->save();
 
-    $entity_resource = $this->buildEntityResource('node', 'article');
+    $entity_resource = $this->buildEntityResource('node', 'article', [
+      'field_relationships' => [new ResourceType('node', 'article', NULL)],
+    ]);
     $response = $entity_resource->patchRelationship($this->node, 'field_relationships', $parsed_field_list, new Request());
 
     // As a side effect, the node will also be saved.
@@ -814,7 +825,9 @@ class EntityResourceTest extends JsonapiKernelTestBase {
       ->grantPermission('edit any article content')
       ->save();
 
-    $entity_resource = $this->buildEntityResource('node', 'article');
+    $entity_resource = $this->buildEntityResource('node', 'article', [
+      'field_relationships' => [new ResourceType('node', 'article', NULL)],
+    ]);
     $response = $entity_resource->deleteRelationship($this->node, 'field_relationships', $parsed_field_list, new Request());
 
     // As a side effect, the node will also be saved.
@@ -826,6 +839,86 @@ class EntityResourceTest extends JsonapiKernelTestBase {
     $this->assertEquals(201, $response->getStatusCode());
   }
 
+  /**
+   * @covers ::getRelated
+   */
+  public function testGetRelatedInternal() {
+    $internal_resource_type = new ResourceType('node', 'article', NULL, TRUE);
+    $resource = $this->buildEntityResource('node', 'article', [
+      'field_relationships' => [$internal_resource_type],
+    ]);
+
+    $this->setExpectedException(NotFoundHttpException::class);
+    $resource->getRelationship($this->node, 'field_relationships', new Request());
+  }
+
+  /**
+   * @covers ::getRelationship
+   */
+  public function testGetRelationshipInternal() {
+    $internal_resource_type = new ResourceType('node', 'article', NULL, TRUE);
+    $resource = $this->buildEntityResource('node', 'article', [
+      'field_relationships' => [$internal_resource_type],
+    ]);
+
+    $this->setExpectedException(NotFoundHttpException::class);
+    $resource->getRelationship($this->node, 'field_relationships', new Request());
+  }
+
+  /**
+   * @covers ::createRelationship
+   */
+  public function testCreateRelationshipInternal() {
+    $internal_resource_type = new ResourceType('node', 'article', NULL, TRUE);
+    $resource = $this->buildEntityResource('node', 'article', [
+      'field_relationships' => [$internal_resource_type],
+    ]);
+
+    Role::load(Role::ANONYMOUS_ID)->grantPermission('edit any article content')->save();
+
+    $field_type_manager = $this->container->get('plugin.manager.field.field_type');
+    $list = $field_type_manager->createFieldItemList($this->node, 'field_relationships');
+
+    $this->setExpectedException(NotFoundHttpException::class);
+    $resource->createRelationship($this->node, 'field_relationships', $list, new Request());
+  }
+
+  /**
+   * @covers ::patchRelationship
+   */
+  public function testPatchRelationshipInternal() {
+    $internal_resource_type = new ResourceType('node', 'article', NULL, TRUE);
+    $resource = $this->buildEntityResource('node', 'article', [
+      'field_relationships' => [$internal_resource_type],
+    ]);
+
+    Role::load(Role::ANONYMOUS_ID)->grantPermission('edit any article content')->save();
+
+    $field_type_manager = $this->container->get('plugin.manager.field.field_type');
+    $list = $field_type_manager->createFieldItemList($this->node, 'field_relationships');
+
+    $this->setExpectedException(NotFoundHttpException::class);
+    $resource->patchRelationship($this->node, 'field_relationships', $list, new Request());
+  }
+
+  /**
+   * @covers ::deleteRelationship
+   */
+  public function testDeleteRelationshipInternal() {
+    $internal_resource_type = new ResourceType('node', 'article', NULL, TRUE);
+    $resource = $this->buildEntityResource('node', 'article', [
+      'field_relationships' => [$internal_resource_type],
+    ]);
+
+    Role::load(Role::ANONYMOUS_ID)->grantPermission('edit any article content')->save();
+
+    $field_type_manager = $this->container->get('plugin.manager.field.field_type');
+    $list = $field_type_manager->createFieldItemList($this->node, 'field_relationships');
+
+    $this->setExpectedException(NotFoundHttpException::class);
+    $resource->deleteRelationship($this->node, 'field_relationships', $list, new Request());
+  }
+
   /**
    * Provides data for the testDeleteRelationship.
    *
@@ -850,11 +943,15 @@ class EntityResourceTest extends JsonapiKernelTestBase {
    *   The entity type ID.
    * @param string $bundle
    *   The bundle.
+   * @param \Drupal\jsonapi\ResourceType\ResourceType[] $relatable_resource_types
+   *   An array of relatable resource types, keyed by field.
+   * @param bool $internal
+   *   Whether the primary resource type is internal.
    *
    * @return \Drupal\jsonapi\Controller\EntityResource
    *   The resource.
    */
-  protected function buildEntityResource($entity_type_id, $bundle) {
+  protected function buildEntityResource($entity_type_id, $bundle, array $relatable_resource_types = [], $internal = FALSE) {
     // The fake route.
     $route = new Route(NULL, [], [
       '_entity_type' => $entity_type_id,
@@ -872,8 +969,11 @@ class EntityResourceTest extends JsonapiKernelTestBase {
     );
     $this->container->set('jsonapi.current_context', $current_context);
 
+    $resource_type = new ResourceType($entity_type_id, $bundle, NULL, $internal);
+    $resource_type->setRelatableResourceTypes($relatable_resource_types);
+
     return new EntityResource(
-      new ResourceType($entity_type_id, $bundle, NULL),
+      $resource_type,
       $this->container->get('entity_type.manager'),
       $this->container->get('entity_field.manager'),
       $current_context,
diff --git a/tests/src/Unit/Routing/RoutesTest.php b/tests/src/Unit/Routing/RoutesTest.php
index f6b2899..5a4396f 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,8 +66,8 @@ 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());
+    // Make sure that there are 4 routes for the non-internal resource.
+    $this->assertEquals(4, $routes->count());
 
     $iterator = $routes->getIterator();
     // Check the collection route.
@@ -130,4 +141,46 @@ 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'],
+      ['jsonapi.entity_type_1--bundle_1_1.relationship'],
+    ];
+  }
+
+  /**
+   * 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_2--bundle_2_1.individual'],
+      ['jsonapi.entity_type_2--bundle_2_1.collection'],
+      ['jsonapi.entity_type_2--bundle_2_1.related'],
+      ['jsonapi.entity_type_2--bundle_2_1.relationship'],
+    ];
+  }
+
 }
