diff --git a/web/modules/example/example.info.yml b/web/modules/example/example.info.yml
new file mode 100644
index 0000000..45cf496
--- /dev/null
+++ b/web/modules/example/example.info.yml
@@ -0,0 +1,3 @@
+name: Example
+type: module
+core_version_requirement: ^11
diff --git a/web/modules/example/example.routing.yml b/web/modules/example/example.routing.yml
new file mode 100644
index 0000000..82ce49d
--- /dev/null
+++ b/web/modules/example/example.routing.yml
@@ -0,0 +1,7 @@
+example.page:
+  path: /example
+  defaults:
+    _controller: Drupal\example\Controller\ExamplePageController
+  methods: [GET]
+  requirements:
+    _permission: access content
diff --git a/web/modules/example/src/Controller/ExamplePageController.php b/web/modules/example/src/Controller/ExamplePageController.php
new file mode 100644
index 0000000..08b6043
--- /dev/null
+++ b/web/modules/example/src/Controller/ExamplePageController.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\example\Controller;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+final class ExamplePageController {
+
+  use StringTranslationTrait;
+
+  public function __invoke(): array {
+    return [
+      '#markup' => $this->t('This page is powered by Drupal and Nix.'),
+    ];
+  }
+
+}
diff --git a/web/modules/example/tests/src/Functional/ExampleTest.php b/web/modules/example/tests/src/Functional/ExampleTest.php
new file mode 100644
index 0000000..bc230c9
--- /dev/null
+++ b/web/modules/example/tests/src/Functional/ExampleTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Tests\example\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+class ExampleTest extends BrowserTestBase {
+
+  public $defaultTheme = 'stark';
+
+  public static $modules = [
+    'example',
+    'node',
+  ];
+
+  public function test_it_loads_the_page(): void {
+    $this->drupalGet('/example');
+
+    $assert = $this->assertSession();
+
+    $assert->statusCodeEquals(200);
+    $assert->pageTextContains('This page is powered by Drupal and Nix.');
+  }
+}