diff --git a/.gitignore b/.gitignore
index 4fbb073..e26c29e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
-/vendor/
+/build/
/composer.lock
+/vendor/
diff --git a/README.md b/README.md
index 29c1f08..042ae05 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# FrugalPHP
+# Frugal
[](https://github.com/clue/frugalphp-incubator/actions)
@@ -10,21 +10,7 @@ Lightweight microframework for fast, event-driven and async-first web applicatio
Take a look at https://ninenines.eu/docs/en/cowboy/2.6/guide/modern_web/
* [Quickstart](#quickstart)
-* [Basics](#basics)
- * [Installation](#installation)
- * [Structure your app (Controllers)](#structure-your-app-controllers)
- * [Testing your app](#testing-your-app)
- * [Deployment](#deployment)
-* [Usage](#usage)
- * [App](#app)
- * [Request](#request)
- * [Response](#response)
- * [Database](#database)
- * [Filesystem](#filesystem)
- * [Authentication](#authentication)
- * [Sessions](#sessions)
- * [Templates](#templates)
- * [Queuing](#queuing)
+* [Documentation](#documentation)
* [Tests](#tests)
* [License](#license)
@@ -38,7 +24,7 @@ First manually change your `composer.json` to include these lines:
"repositories": [
{
"type": "vcs",
- "url": "https://github.com/clue/frugalphp"
+ "url": "https://github.com/clue-engineering/frugal"
}
]
}
@@ -49,7 +35,7 @@ First manually change your `composer.json` to include these lines:
Simply install FrugalPHP:
```bash
-$ composer require clue/frugal:dev-master
+$ composer require clue/frugal:dev-main
```
> TODO: Tagged release.
@@ -98,232 +84,34 @@ HTTP/1.1 200 OK
Hello wörld!
```
-## Basics
-
-### Installation
-
-* Runs everywhere
-* Requires only PHP 7.1+, no extensions required
-* Can run behind existing web servers or locally with built-in webserver (see deployment)
-
-[…]
-
-### Structure your app (Controllers)
-
-Once everything is up and running, we can take a look at how to best structure
-our actual web application.
-
-To get started, it's often easiest to start with simple closure definitions
-like the following:
-
-```php
-get('/', function () {
- return new React\Http\Message\Response(
- 200,
- [],
- "Hello wörld!\n"
- );
-});
-
-$app->get('/users/{name}', function (Psr\Http\Message\ServerRequestInterface $request) {
- return new React\Http\Message\Response(
- 200,
- [],
- "Hello " . $request->getAttribute('name') . "!\n"
- );
-});
-
-$loop->run();
-```
-
-While easy to get started, this will easily get out of hand for more complex
-business domains when you have more than a couple of routes registered.
-
-For real-world applications, we highly recommend structuring your application
-into invidividual controller classes. This way, we can break up the above
-definition into three even simpler files:
-
-```php
-# main.php
-get('/', new Acme\Todo\HelloController());
-$app->get('/users/{name}', new Acme\Todo\UserController());
-
-$loop->run();
-```
-
-```php
-# src/HelloController.php
-getAttribute('name') . "!\n"
- );
- }
-}
-```
-
-Doesn't look too complex, right? Now, we only need to tell Composer's autoloader
-about our vendor namespace `Acme\\Todo` in the `src/` folder. Make sure to include
-the following lines in your `composer.json` file:
-
-```json
-{
- "autoload": {
- "psr-4": {
- "Acme\\Todo\\": "src/"
- }
- }
-}
-```
-
-When we're doing this the first time, we have to update Composer's generated
-autoloader classes:
-
-```bash
-$ composer dump-autoload
-```
-
-Don't worry, that's a one-time setup only. If you're used to working with
-Composer, this shouldn't be too surprising. If this sounds new to you, rest
-assured this is the only time you have to worry about this, new classes can
-simply be added without having to run Composer again.
-
-Again, let's see our web application still works by using your favorite
-webbrowser or command line tool:
-
-```bash
-$ curl -v http://localhost:8080/
-HTTP/1.1 200 OK
-…
-
-Hello wörld!
-```
-
-### Testing your app
-
-**We ❤️ TDD and DDD!**
-
-New to testing your web application? While we don't want to *force* you to test
-your app, we want to emphasize the importance of automated test suits and try hard
-to make testing your web application as easy as possible.
-
-Once your app is structured into dedicated controller classes as per the previous
-chapter, […]
-
-> TODO: PHPUnit setup basics and first test cases.
-
-> TODO: Higher-level functional tests.
-
-### Deployment
-
-Runs everywhere:
-
-* Built-in webserver
-* nginx with PHP-FPM
-* Apache with mod_fcgid and PHP-FPM
-* Apache with mod_php
-* PHP's development webserver
-
-[…]
-
-## Usage
-
-### App
-
-* Batteries included, but swappable
-* Providing HTTP routing (RESTful applications)
-
-### Request
-
-* PSR-7
-
-### Response
-
-* PSR-7
-
-### Database
-
-* Async
-* No PDO, no Doctrine and family
-* Easy to spot, harder to replace
-* MySQL, Postgres and SQLite supported
-* ORM
-* Redis
-
-### Filesystem
-
-* Async
-* No `fopen()`, `file_get_contents()` and family
-* Easy to overlook
-* Few blocking calls *can* be acceptable
-
-### Authentication
-
-* Basic auth easy
-* HTTP middleware better
-* JWT and oauth possible
-
-### Sessions
-
-* Built-in (or module?)
-* HTTP middleware
-* Persistence via database/ORM or other mechanism?
-
-### Templates
-
-* Any template language possible
-* Twig recommended?
-
-### Queuing
-
-* Built-in (or module?)
-* Redis built-in, but swappable with real instance (constraints?)
+## Documentation
+
+Hooked?
+See [full documentation](docs/) for more details.
+
+> We use [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) to
+> render our documentation to a pretty HTML version.
+>
+> If you want to contribute to the documentation, it's easiest to just run
+> this in a Docker container like this:
+>
+> ```bash
+> $ docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material
+> ```
+>
+> You can access the documentation via `http://localhost:8000`.
+> If you want to generate a static HTML folder for deployment, you can again
+> use a Docker container like this:
+>
+> ```bash
+> $ docker run --rm -it -v ${PWD}:/docs squidfunk/mkdocs-material build
+> ```
+>
+> The resulting `build/docs/` should then be deployed behind a web server (tbd).
+> If you want to add a new documentation file and/or change the page order, make sure the [`mkdocs.yml`](mkdocs.yml)
+> file contains an up-to-date list of all pages.
+>
+> Happy hacking!
## Tests
@@ -342,7 +130,7 @@ $ php vendor/bin/phpunit
Additionally, you can run some simple acceptance tests to verify the framework
examples work as expected behind your web server. Use your web server of choice
-(see [deployment](#deployment)) and execute the tests with the URL to your
+(see deployment documentation) and execute the tests with the URL to your
installation like this:
```bash
diff --git a/docs/api/app.md b/docs/api/app.md
new file mode 100644
index 0000000..9a4826d
--- /dev/null
+++ b/docs/api/app.md
@@ -0,0 +1,153 @@
+# App
+
+The `App` class is your main entrypoint to any application that builds on top of X.
+It provides a simple API for routing HTTP requests as commonly used in RESTful applications.
+
+Internally, the `App` object builds on top of [ReactPHP](https://reactphp.org/)
+to do its magic, hence you have to create it like this:
+
+```php
+run();
+$loop->run();
+```
+
+> ℹ️ **Heads up!**
+>
+> Major improvements upcoming! We're actively contributing to our underlying
+> libraries to make sure this can look like this in the near future:
+>
+> ```php
+>
+> require __DIR__ . '/vendor/autoload.php';
+>
+> $app = 🚀🚀🚀\App();
+>
+> // Register routes here, see routing…
+>
+> $app->run();
+> ```
+
+## Routing
+
+The `App` class offers a number of API methods that allow you to route incoming
+HTTP requests to controller functions. In its most simple form, you can add
+multiple routes using inline closures like this:
+
+```php
+$app->get('/user', function () {
+ return new React\Http\Message\Response(200, [], "hello everybody!");
+});
+
+$app->get('/user/{id}', function (Psr\Http\Message\ServerRequestInterface $request) {
+ $id = $request->getAttribute('id');
+ return new React\Http\Message\Response(200, [], "hello $id");
+});
+```
+
+For example, an HTTP `GET` request for `/user` would call the first controller
+function.
+An HTTP `GET` request for `/user/alice` would call the second controller function
+which also highlights how you can use [request attributes](request.md#attributes)
+to access values from URI templates.
+
+An HTTP `GET` request for `/foo` would automatically reject the HTTP request with
+a 404 (Not Found) error response unless this route is registered.
+Likewise, an HTTP `POST` request for `/user` would reject with a 405 (Method Not
+Allowed) error response unless a route for this method is also registered.
+
+You can route any number of incoming HTTP requests to controller functions by
+using the matching API methods like this:
+
+```php
+$app->get('/user/{id}', $controller);
+$app->head('/user/{id}', $controller);
+$app->post('/user/{id}', $controller);
+$app->put('/user/{id}', $controller);
+$app->patch('/user/{id}', $controller);
+$app->delete('/user/{id}', $controller);
+$app->options('/user/{id}', $controller);
+```
+
+If you want to map multiple HTTP request methods to a single controller, you can
+use this shortcut instead of listing each method explicitly like above:
+
+```
+$app->map(['GET', 'POST'], '/user/{id}', $controller);
+```
+
+If you want to map each and every HTTP request method to a single controller,
+you can use this additional shortcut:
+
+```
+$app->any('/user/{id}', $controller);
+```
+
+## Controllers
+
+The above examples use inline closures as controller functions to make these
+examples more concise:
+
+```
+$app->get('/', function () {
+ return new React\Http\Message\Response(
+ 200,
+ [],
+ "Hello wörld!\n"
+ );
+});
+```
+
+While easy to get started, it's easy to see how this would become a mess once
+you keep adding more controllers to a single application.
+For this reason, we recommend using [controller classes](../best-practices/controllers.md)
+for production use-cases like this:
+
+```php
+# main.php
+$app->get('/', new Acme\Todo\HelloController());
+```
+
+```php
+# src/HelloController.php
+ ℹ️ **Feature preview**
+>
+> This is a feature preview, i.e. it might not have made it into the current beta.
+> Give feedback to help us prioritize.
+
+One of the main features of the `App` is middleware support.
+Middleware allows you to extract common functionality such as HTTP login, session handling or logging into reusable components.
+These middleware components can be added to both individual routes or globally to all registered routes.
+See [middleware documentation](../04-middleware.md) for more details.
diff --git a/docs/api/middleware.md b/docs/api/middleware.md
new file mode 100644
index 0000000..e7a4b53
--- /dev/null
+++ b/docs/api/middleware.md
@@ -0,0 +1,88 @@
+# Middleware
+
+> ℹ️ **Feature preview**
+>
+> This is a feature preview, i.e. it might not have made it into the current beta.
+> Give feedback to help us prioritize.
+
+One of the main features of X is middleware support.
+Middleware allows you to extract common functionality such as an HTTP login, session handling or logging into reusable components.
+
+To get started, we can add an example middleware handler to an individual route
+by adding an additional callable before the final controller like this:
+
+```php hl_lines="3-6"
+$app->get(
+ '/user',
+ function (Psr\Http\Message\ServerRequestInterface $request, callable $next) {
+ $request = $request->withAttribute('admin', false);
+ return $next($request);
+ },
+ function (Psr\Http\Message\ServerRequestInterface $request) {
+ $role = $request->getAttribute('admin') ? 'admin' : 'user';
+ return new React\Http\Message\Response(200, [], "hello $role!");
+ }
+);
+```
+
+For example, an HTTP `GET` request for `/user` would first call the middleware handler which then modifies this request and passes the modified request to the next controller function.
+
+While easy to get started, it's easy to see how this would become a mess once you
+keep adding more controllers to a single application.
+For this reason, we recommend using middleware classes for production use-cases
+like this:
+
+```php hl_lines="8"
+# main.php
+
+use Acme\Todo\AdminMiddleware;
+use Acme\Todo\UserController;
+
+// …
+
+$app->get('/user', new AdminMiddleware(), new UserController());
+```
+
+```php
+# src/AdminMiddleware.php
+withAttribute('admin', false);
+ return $next($request);
+ }
+}
+```
+
+Likewise, you can add any number of middleware handlers to each route.
+Each middleware is responsible for calling the next handler in the chain or
+directly returning an error response if the request should not be processed.
+
+Additionally, you can also add middleware to the `App` object itself to register
+a global middleware handler for all registered routes:
+
+```php hl_lines="7"
+get('/user', new UserController());
+
+$app->run();
+$loop->run();
+```
+
+You can also combine global middleware handlers (think logging) with additional
+middleware handlers for individual routes (think authentication).
+Global middleware handlers will always be called before route middleware handlers.
diff --git a/docs/api/request.md b/docs/api/request.md
new file mode 100644
index 0000000..033b688
--- /dev/null
+++ b/docs/api/request.md
@@ -0,0 +1,201 @@
+# Request
+
+Whenever the client sends an HTTP request to our application,
+we receive this as an request object and need to react to it.
+
+We love standards and want to make using X as simple as possible.
+That's why we build on top of the established [PSR-7 standard](https://www.php-fig.org/psr/psr-7/)
+(HTTP message interfaces).
+This standard defines common interfaces for HTTP request and response objects.
+
+If you've ever used PSR-7 before, you should immediately feel at home when using X.
+If you're new to PSR-7, don't worry.
+Here's everything you need to know to get started.
+
+> ℹ️ **A note about other PSR-7 implementations**
+>
+> This documentation uses the
+> [`Psr\Http\Message\ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface)
+> for all examples.
+> The actual class implementing this interface is an implementation detail that
+> should not be relied upon.
+> If you need to construct your own instance, we recommend using the
+> [`React\Http\Message\ServerRequest`](https://reactphp.org/http/#serverrequest)
+> class because this comes bundled as part of our dependencies,
+> but you may use any other implementation as long as
+> it implements the same interface.
+
+## Attributes
+
+You can access request attributes like this:
+
+```php
+$app->get('/user/{id}', function (Psr\Http\Message\ServerRequestInterface $request) {
+ $id = $request->getAttribute('id');
+
+ return new React\Http\Message\Response(200, [], "Hello $id");
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash
+$ curl http://localhost:8080/user/Alice
+Hello Alice
+```
+
+These custom attributes are most commonly used when using URI placeholders
+from [routing](app.md#routing).
+Each placeholder will automatically be assigned to a matching request attribute.
+See also [routing](app.md#routing) for more details.
+
+Additionally, these custom attributes can also be useful when passing additional
+information from a middleware handler to other handlers further down the chain
+(think authentication information).
+See also [middleware](middleware.md) for more details.
+
+## JSON
+
+You can access JSON data from the HTTP request body like this:
+
+```php
+$app->post('/user', function (Psr\Http\Message\ServerRequestInterface $request) {
+ $data = json_decode((string) $request->getBody());
+ $name = $data->name ?? 'anonymous';
+
+ return new React\Http\Message\Response(200, [], "Hello $name");
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash
+$ curl http://localhost:8080/user --data '{"name":"Alice"}'
+Hello Alice
+```
+
+Additionally, you may want to validate the `Content-Type: application/json` request header
+to be sure the client intended to send a JSON request body.
+
+This example returns a simple text response, you may also want to return a
+[JSON response](response.md#json) for common API usage.
+
+## Form data
+
+You can access HTML form data from the HTTP request body like this:
+
+```php
+$app->post('/user', function (Psr\Http\Message\ServerRequestInterface $request) {
+ $data = $request->getParsedBody();
+ $name = $data['name'] ?? 'Anonymous';
+
+ return new React\Http\Message\Response(200, [], "Hello $name");
+});
+```
+
+
+An HTTP request can be sent like this:
+
+```bash
+$ curl http://localhost:8080/user -d name=Alice
+Hello Alice
+```
+
+This method returns a possibly nested array of form fields, very similar to
+PHP's `$_POST` superglobal.
+
+## Uploads
+
+You can access any file uploads from HTML forms like this:
+
+```php
+$app->post('/user', function (Psr\Http\Message\ServerRequestInterface $request) {
+ $files = $request->getUploadedFiles();
+ $name = isset($files['image']) ? $files['image']->getClientFilename() : 'x';
+
+ return new React\Http\Message\Response(200, [], "Uploaded $name");
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash
+$ curl http://localhost:8080/user -F image=@~/Downloads/image.jpg
+Uploaded image.jpg
+```
+
+This method returns a possibly nested array of files uploaded, very similar
+to PHP's `$_FILES` superglobal.
+Each file in this array implements the `Psr\Http\Message\UploadedFileInterface`:
+
+```php
+$files = $request->getUploadedFiles();
+$image = $files['image'];
+assert($image instanceof Psr\Http\Message\UploadedFileInterface);
+
+$stream = $image->getStream();
+assert($stream instanceof Psr\Http\Message\StreamInterface);
+$contents = (string) $stream;
+
+$size = $image->getSize();
+assert(is_int($size));
+
+$name = $image->getClientFilename();
+assert(is_string($name) || $name === null);
+
+$type = $image->getClientMediaType();
+assert(is_string($type) || $name === null);
+```
+
+> ℹ️ **Info**
+>
+> Note that HTTP requests are currently limited to 64 KiB. Any uploads above
+> this size will currently show up as an empty request body with no file uploads
+> whatsoever.
+
+## Headers
+
+You can access all HTTP request headers like this:
+
+```php
+$app->get('/user', function (Psr\Http\Message\ServerRequestInterface $request) {
+ $agent = $request->getHeaderLine('User-Agent');
+
+ return new React\Http\Message\Response(200, [], "Hello $agent");
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash
+$ curl http://localhost:8080/user -H 'User-Agent: FrameworkX/0'
+Hello FrameworkX/0
+```
+
+This example returns a simple text response with no additional response headers,
+you may also want to return [response headers](response.md#headers) for common API usage.
+
+## Parameters
+
+You can access server-side parameters like this:
+
+```php
+$app->get('/user', function (Psr\Http\Message\ServerRequestInterface $request) {
+ $params = $request->getServerParams();
+ $ip = $params['REMOTE_ADDR'] ?? 'unknown';
+
+ return new React\Http\Message\Response(200, [], "Hello $ip");
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash
+$ curl http://localhost:8080/user
+Hello 127.0.0.1
+```
+
+This method returns an array of server-side parameters, very similar
+to PHP's `$_SERVER` superglobal.
+Note that available server parameters depend on the server software and version
+in use.
diff --git a/docs/api/response.md b/docs/api/response.md
new file mode 100644
index 0000000..0eaa78a
--- /dev/null
+++ b/docs/api/response.md
@@ -0,0 +1,238 @@
+# Response
+
+Whenever the client sends an HTTP request to our application,
+we need to send back an HTTP response message.
+
+We love standards and want to make using X as simple as possible.
+That's why we build on top of the established [PSR-7 standard](https://www.php-fig.org/psr/psr-7/)
+(HTTP message interfaces).
+This standard defines common interfaces for HTTP request and response objects.
+
+If you've ever used PSR-7 before, you should immediately feel at home when using X.
+If you're new to PSR-7, don't worry.
+Here's everything you need to know to get started.
+
+> ℹ️ **A note about other PSR-7 implementations**
+>
+> All of the examples in this documentation use the
+> [`React\Http\Message\Response`](https://reactphp.org/http/#response) class
+> because this comes bundled as part of our dependencies.
+> If you have more specific requirements or want to integrate this with an
+> existing piece of code, you can use any response implementation as long as
+> it implements the [`Psr\Http\Message\ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface).
+
+## JSON
+
+You can send JSON data as an HTTP response body like this:
+
+```php
+$app->get('/user', function () {
+ $data = [
+ [
+ 'name' => 'Alice'
+ ],
+ [
+ 'name' => 'Bob'
+ ]
+ ];
+
+ return new React\Http\Message\Response(
+ 200,
+ ['Content-Type' => 'application/json'],
+ json_encode($data)
+ );
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash
+$ curl http://localhost:8080/user
+[{"name":"Alice"},{"name":"Bob"}]
+```
+
+If you want to return pretty-printed JSON, all you need to do is passing the
+correct flags when encoding:
+
+```php hl_lines="14-17"
+$app->get('/user', function () {
+ $data = [
+ [
+ 'name' => 'Alice'
+ ],
+ [
+ 'name' => 'Bob'
+ ]
+ ];
+
+ return new React\Http\Message\Response(
+ 200,
+ ['Content-Type' => 'application/json'],
+ json_encode(
+ $data,
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
+ )
+ );
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash
+$ curl http://localhost:8080/user
+[
+ {
+ "name": "Alice"
+ },
+ {
+ "name":"Bob"
+ }
+]
+```
+
+This example returns a simple JSON response from some static data.
+In real-world applications, you may want to load this from a
+[database](../integrations/database.md).
+For common API usage, you may also want to receive a [JSON request](request.md#json).
+
+## HTML
+
+You can send HTML data as an HTTP response body like this:
+
+```php
+$app->get('/user', function () {
+ $html = <<Hello Alice
+HTML;
+
+ return new React\Http\Message\Response(
+ 200,
+ ['Content-Type' => 'text/html; charset=utf-8'],
+ $html
+ );
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash
+$ curl http://localhost:8080/user
+
Hello Alice
+```
+
+This example returns a simple HTML response from some static data.
+In real-world applications, you may want to load this from a
+[database](../integrations/database.md) and perhaps use
+[templates](../integrations/templates.md) to render your HTML.
+
+## Status Codes
+
+You can assign status codes like this:
+
+```php hl_lines="5 12"
+$app->get('/user/{id}', function (Psr\Http\Message\ServerRequestInterface $request) {
+ $id = $request->getAttribute('id');
+ if ($id === 'admin') {
+ return new React\Http\Message\Response(
+ 403,
+ [],
+ 'Forbidden'
+ );
+ }
+
+ return new React\Http\Message\Response(
+ 200,
+ [],
+ "Hello $id"
+ );
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash hl_lines="2 6"
+$ curl http://localhost:8080/user/Alice -I
+HTTP/1.1 200 OK
+…
+
+$ curl http://localhost:8080/user/admin -I
+HTTP/1.1 403 Forbidden
+…
+```
+
+Each HTTP response message contains a status code that describes whether the
+HTTP request has been successfully completed.
+Here's a list of the most common HTTP status codes:
+
+* 200 (OK)
+* 301 (Permanent Redirect)
+* 302 (Temporary Redirect)
+* 403 (Forbidden)
+* 404 (Not Found)
+* 500 (Internal Server Error)
+* …
+
+See [list of HTTP status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) for more details.
+
+## Headers
+
+You can assign HTTP response headers like this:
+
+```php hl_lines="4"
+$app->get('/user', function () {
+ return new React\Http\Message\Response(
+ 200,
+ ['Content-Type' => 'text/plain; charset=utf-8'],
+ "Hello $id"
+ );
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash hl_lines="3"
+$ curl http://localhost:8080/user -I
+HTTP/1.1 200 OK
+Content-Type: text/plain; charset=utf-8
+Server: ReactPHP/1
+Date: Fri, 30 Apr 2021 08:51:04 GMT
+Content-Length: 88
+Connection: close
+```
+
+Each HTTP response message can contain an arbitrary number of response headers.
+You can pass these headers as an associative array to the response object.
+
+Additionally, the application will automatically include default headers required
+by the HTTP protocol.
+It's not recommended to mess with these default headers unless you're sure you
+know what you're doing.
+
+## Internal Server Error
+
+Each controller function needs to return a response object in order to send
+an HTTP response message.
+If the controller functions throws an `Exception` (or `Throwable`) or any other type, the
+HTTP request will automatically be rejected with a 500 (Internal Server Error)
+HTTP error response:
+
+```php
+$app->get('/user', function () {
+ // TODO: load data
+ throw new BadMethodCallException();
+});
+```
+
+An HTTP request can be sent like this:
+
+```bash hl_lines="2"
+$ curl http://localhost:8080/user -I
+HTTP/1.1 500 Internal Server Error
+…
+```
+
+This error message contains only few details to the client to avoid leaking
+internal information.
+If you want to implement custom error handling, you're recommended to either
+catch any exceptions your own or use a [middleware handler](middleware.md) to
+catch any exceptions in your application.
diff --git a/docs/async/child-processes.md b/docs/async/child-processes.md
new file mode 100644
index 0000000..37ed21a
--- /dev/null
+++ b/docs/async/child-processes.md
@@ -0,0 +1,13 @@
+# Parallel processing with child processes
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* Avoid blocking by moving blocking implementation to child process
+* Child process I/O for communication
+* Multithreading, but isolated processes
+* See [reactphp/child-process](https://reactphp.org/child-process/) for underlying APIs
+* See [clue/reactphp-pq](https://github.com/clue/reactphp-pq) for higher-level API to automatically wrap blocking functions in an async child process and turn blocking functions into non-blocking [promises](promises.md)
diff --git a/docs/async/coroutines.md b/docs/async/coroutines.md
new file mode 100644
index 0000000..38acbb0
--- /dev/null
+++ b/docs/async/coroutines.md
@@ -0,0 +1,15 @@
+# Coroutines
+
+> ⚠️ **Feature preview**
+>
+> This is a feature preview, i.e. it might not have made it into the current beta.
+> Give feedback to help us prioritize.
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* [Promises](promises.md) can be hard due to nested callbacks
+* X provides Generator-based coroutines
+* Synchronous code structure, yet asynchronous execution
+* Generators can be a bit harder to understand, see [Fibers](fibers.md) for future PHP 8.1 API.
diff --git a/docs/async/fibers.md b/docs/async/fibers.md
new file mode 100644
index 0000000..1fb5610
--- /dev/null
+++ b/docs/async/fibers.md
@@ -0,0 +1,18 @@
+# Fibers
+
+> ⚠️ **Feature preview**
+>
+> This is a feature preview, i.e. it might not have made it into the current beta.
+> Give feedback to help us prioritize.
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* Hot topic for PHP 8.1
+* Async APIs that look just like their synchronous counterparts
+* Much easier to integrate, possibly larger ecosystem in future
+* Requires PHP 8.1 (November 2021), adoption will take time
+* [Promises](promises.md) still required for concurrent execution
+* [Promises](promises.md) and [Coroutines](coroutines.md) work just fine until ecosystem matures
+* See [blog post](https://clue.engineering/2021/fibers-in-php)
diff --git a/docs/async/promises.md b/docs/async/promises.md
new file mode 100644
index 0000000..c625d30
--- /dev/null
+++ b/docs/async/promises.md
@@ -0,0 +1,14 @@
+# Promises
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* Avoid blocking ([databases](../integrations/database.md), [filesystem](../integrations/filesystem.md), etc.)
+* Deferred execution
+* Concurrent execution more efficient than [multithreading](child-processes.md)
+* API can be a bit harder (see [Coroutines](coroutines.md) or [Fibers](fibers.md))
+* See [reactphp/promise](https://reactphp.org/promise/)
+* Avoid blocking by moving blocking implementation to [child process](child-processes.md)
diff --git a/docs/async/streaming.md b/docs/async/streaming.md
new file mode 100644
index 0000000..6211394
--- /dev/null
+++ b/docs/async/streaming.md
@@ -0,0 +1,25 @@
+# Streaming
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+Processing large amounts of data or when data arrives at future time.
+
+## Streaming downloads
+
+* Efficient processing of large files without keeping contents in memory
+* Similar for video streaming, but not scope of this project
+
+## EventSource
+
+* HTML5 Server-Sent Events (SSE) aka. EventSource supported out-of-the-box
+* Live streaming, live data, realtime communication
+
+## WebSockets
+
+* HTML5 WebSockets integration supported with [Ratchet](http://socketo.me/)
+* See also EventSource as alternative
+* Bidirectional communication
diff --git a/docs/best-practices/controllers.md b/docs/best-practices/controllers.md
new file mode 100644
index 0000000..25e8167
--- /dev/null
+++ b/docs/best-practices/controllers.md
@@ -0,0 +1,144 @@
+# Controller classes to structure your app
+
+When starting with X, it's often easiest to start with simple closure definitions like suggested in the [quickstart guide](../getting-started/quickstart.md).
+
+As a next step, let's take a look at how this structure can be improved with controller classes.
+This is especially useful once you leave the prototyping phase and want to find the best structure for a production-ready setup.
+
+To get started, let's take a look at the following simple closure definitions:
+
+```php
+# app.php
+get('/', function () {
+ return new React\Http\Message\Response(
+ 200,
+ [],
+ "Hello wörld!\n"
+ );
+});
+
+$app->get('/users/{name}', function (Psr\Http\Message\ServerRequestInterface $request) {
+ return new React\Http\Message\Response(
+ 200,
+ [],
+ "Hello " . $request->getAttribute('name') . "!\n"
+ );
+});
+
+$app->run();
+$loop->run();
+```
+
+While easy to get started, it's also easy to see how this will get out of hand for more complex
+business domains when you have more than a couple of routes registered.
+
+For real-world applications, we highly recommend structuring your application
+into invidividual controller classes. This way, we can break up the above
+definition into three even simpler files:
+
+```php
+# app.php
+get('/', new Acme\Todo\HelloController());
+$app->get('/users/{name}', new Acme\Todo\UserController());
+
+$app->run();
+$loop->run();
+```
+
+```php
+# src/HelloController.php
+getAttribute('name') . "!\n"
+ );
+ }
+}
+```
+
+Doesn't look too complex, right? Now, we only need to tell Composer's autoloader
+about our vendor namespace `Acme\\Todo` in the `src/` folder. Make sure to include
+the following lines in your `composer.json` file:
+
+```json
+{
+ "autoload": {
+ "psr-4": {
+ "Acme\\Todo\\": "src/"
+ }
+ }
+}
+```
+
+When we're doing this the first time, we have to update Composer's generated
+autoloader classes:
+
+```bash
+$ composer dump-autoload
+```
+
+> ℹ️ **New to Composer?**
+>
+> Don't worry, that's a one-time setup only. If you're used to working with
+Composer, this shouldn't be too surprising. If this sounds new to you, rest
+assured this is the only time you have to worry about this, new classes can
+simply be added without having to run Composer again.
+
+Again, let's see our web application still works by using your favorite
+webbrowser or command line tool:
+
+```bash
+$ curl -v http://localhost:8080/
+HTTP/1.1 200 OK
+…
+
+Hello wörld!
+```
+
+If everything works as expected, we can continue with writing our first tests to automate this.
diff --git a/docs/best-practices/deployment.md b/docs/best-practices/deployment.md
new file mode 100644
index 0000000..625aafb
--- /dev/null
+++ b/docs/best-practices/deployment.md
@@ -0,0 +1,85 @@
+# Production deployment
+
+One of the nice properties of X is that it **runs anywhere**, i.e. it works both
+behind traditional web server setups as well as in a stand-alone environment.
+This makes it easy to get started with existing web application stacks, yet it
+provides even more awesome features with its built-in web server.
+
+## Traditional stacks
+
+No matter what existing PHP stack you're using, X runs anywhere.
+This means that if you've already used PHP before, X will *just work*.
+
+* nginx with PHP-FPM
+* Apache with PHP-FPM, mod_fcgid, mod_cgi or mod_php
+* Any other web server using FastCGI to talk to PHP-FPM
+* Linux, Mac and Windows operating systems (LAMP, MAMP, WAMP)
+
+*We've got you covered.*
+
+For example, if you've followed the [quickstart guide](../getting-started/quickstart.md), you can run this using PHP's built-in development web
+server for testing purposes like this:
+
+```bash
+$ php -S 0.0.0.0:8080 app.php
+```
+
+In order to check your web application responds as expected, you can use your favorite webbrowser or command line tool:
+
+```bash
+$ curl -v http://localhost:8080/
+HTTP/1.1 200 OK
+…
+
+Hello wörld!
+```
+
+## Built-in web server
+
+But there's more!
+Framework X ships its own efficient web server implementation written in pure PHP.
+This uses an event-driven architecture to allow you to get the most out of Framework X.
+With the built-in web server, we provide a non-blocking implementation that can handle thousands of incoming connections and provide a much better user experience in high-load scenarios.
+
+With no changes required, you can run the built-in web server with the exact same code base on the command line:
+
+```bash
+$ php app.php
+```
+
+Let's take a look and see this works just like before:
+
+```bash
+$ curl -v http://localhost:8080/
+HTTP/1.1 200 OK
+…
+
+Hello wörld!
+```
+
+You may be wondering how fast a pure PHP web server implementation could possibly be.
+
+```
+$ ab -n10000 -c10 http://localhost:8080/
+…
+Concurrency Level: 10
+Time taken for tests: 0.991 seconds
+Complete requests: 10000
+Failed requests: 0
+Total transferred: 1090000 bytes
+HTML transferred: 130000 bytes
+Requests per second: 10095.17 [#/sec] (mean)
+Time per request: 0.991 [ms] (mean)
+Time per request: 0.099 [ms] (mean, across all concurrent requests)
+Transfer rate: 1074.58 [Kbytes/sec] received
+```
+
+The answer: Very fast!
+
+If you're going to use this in production, we still recommend running this
+behind a reverse proxy such as nginx, HAproxy, etc. for TLS termination
+(HTTPS support).
+
+Additionally, you should use service monitoring to make sure the server will
+automatically restart after system reboot or failure. Docker containers or
+systemd unit files would be common solutions here.
diff --git a/docs/best-practices/testing.md b/docs/best-practices/testing.md
new file mode 100644
index 0000000..513a628
--- /dev/null
+++ b/docs/best-practices/testing.md
@@ -0,0 +1,199 @@
+# Testing
+
+> ℹ️ **New to testing your web application?**
+>
+> While we don't want to *force* you to test your app, we want to emphasize the
+> importance of automated test suites and try hard to make testing your web
+> application as easy as possible.
+>
+> Tests allow you to verify correct behavior of your implementation, so that you
+> match expected behavior with the actual implementation.
+> And perhaps more importantly, by automating this process you can be sure
+> future changes do not introduce any regressions and suddenly break something else.
+> *Develop your application with ease and certainty.*
+>
+> **We ❤️ TDD!**
+
+## PHPUnit basics
+
+Once your app is structured into [dedicated controller classes](controllers.md)
+as per the previous chapter, we can test each controller class in isolation.
+This way, testing becomes pretty straight forward.
+
+Let's start simple and write some unit tests for our simple `HelloController` class:
+
+```php
+# src/HelloController.php
+ ℹ️ **New to PHPUnit?**
+>
+> If you haven't heard about [PHPUnit](https://phpunit.de/) before,
+> PHPUnit is *the* testing framework for PHP projects.
+> After installing it as a development dependency, we can take advantage of its
+> structure to write tests for our own application.
+
+Next, we can start by creating our first unit test:
+
+```php
+# tests/HelloControllerTest.php
+assertInstanceOf(ResponseInterface::class, $response);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals("Hello wörld!\n", (string) $response->getBody());
+ }
+}
+```
+
+We're intentionally starting simple.
+By starting with a controller class following a somewhat trivial implementation,
+we can focus on just getting the test suite up and running first.
+All following tests will also follow a somewhat similar structure, so we can
+always use this as a simple building block:
+
+* create an HTTP request object
+* pass it into our controller function
+* and then run assertions on the expected HTTP response object.
+
+Once you've created your first unit tests, it's time to run PHPUnit by executing
+this command in the project directory:
+
+```
+$ vendor/bin/phpunit tests
+PHPUnit 9.5.4 by Sebastian Bergmann and contributors.
+
+. 1 / 1 (100%)
+
+Time: 00:00.006, Memory: 4.00 MB
+
+OK (1 test, 1 assertion)
+```
+
+## Testing with specific requests
+
+Once the basic test setup works, let's continue with testing a controller that
+shows different behavior depending on what HTTP request comes in.
+For this example, we're using [request attributes](../api/request.md#attributes),
+but the same logic applies to testing different URLs, HTTP request headers, etc.:
+
+```php
+# src/UserController.php
+getAttribute('name') . "!\n"
+ );
+ }
+}
+```
+
+Again, we create a new test class matching the controller class:
+
+```php
+# tests/UserControllerTest.php
+withAttribute('name', 'Alice');
+
+ $controller = new UserController();
+ $response = $controller($request);
+
+ $this->assertInstanceOf(ResponseInterface::class, $response);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals("Hello Alice!\n", (string) $response->getBody());
+ }
+}
+```
+
+This follows the exact same logic like the previous example, except this time
+we're setting up a specific HTTP request and asserting the HTTP response
+contains the correct name.
+Again, we can run PHPUnit in the project directory to see this works as expected:
+
+```
+$ vendor/bin/phpunit tests
+PHPUnit 9.5.4 by Sebastian Bergmann and contributors.
+
+.. 2 / 2 (100%)
+
+Time: 00:00.003, Memory: 4.00 MB
+
+OK (2 tests, 2 assertions)
+```
+
+## Further reading
+
+If you've made it this far, you should have a basic understanding about how
+testing can help you *develop your application with ease and certainty*.
+We believe mastering TTD is well
+worth it, but perhaps this is somewhat out of scope for this documentation.
+If you're curious, we recommend looking into the following topics:
+
+* TDD
+* Higher-level functional tests
+* Test automation
+* CI / CD
diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md
new file mode 100644
index 0000000..0b2455f
--- /dev/null
+++ b/docs/getting-started/quickstart.md
@@ -0,0 +1,114 @@
+# Quickstart in 5 minutes
+
+Getting started with X is easy!
+Here's a quick tutorial to get you up and running in 5 minutes or less.
+Start your timer and here we go!
+
+## Code
+
+In order to first start using X, let's start with an entirely empty project directory.
+This shouldn't be too confusing, but here's how you can do so on the common line:
+
+```bash
+$ mkdir ~/projects/acme
+$ cd ~/projects/acme
+```
+
+Next, we can start by taking a look at a simple example application.
+You can use this example to get started by creating a new `app.php` file in your empty project directory:
+
+```php
+get('/', function () {
+ return new React\Http\Message\Response(
+ 200,
+ [],
+ "Hello wörld!\n"
+ );
+});
+
+$app->run();
+$loop->run();
+```
+
+On a code level, this is everything you need to get started.
+For more sophisticated projects, you may want to make sure to [structure your controllers](../best-practices/controllers.md),
+but the above should be just fine for starters.
+
+## Installation
+
+Next, we need to install X and its dependencies to actually run this project.
+
+> ⚠️ **Beta**
+>
+> This project is currently in closed beta, so the installation requires a manual step first.
+> This will not be necessary once the project is released to the public.
+>
+> Start by creating a new `composer.json` in the project directory with the following contents:
+>
+> ```json
+> {
+> "repositories": [
+> {
+> "type": "vcs",
+> "url": "https://github.com/clue-engineering/frugal"
+> }
+> ]
+> }
+> ```
+
+Thanks to [Composer](https://getcomposer.org/), this installation only requires a single command.
+
+> ℹ️ **New to Composer?**
+>
+> If you haven't heard about Composer before, Composer is *the* package manager for PHP-based projects.
+> You can think of it as what NPM is to JavaScript, *but better*.
+> If you haven't used it before, you have to install a recent PHP version and Composer before you can proceed.
+> On Ubuntu- or Debian-based systems, this would be as simple as this:
+>
+> ```bash
+> $ sudo apt install php-cli php-mbstring php-xml composer
+> ```
+
+In your project directory, simply run the following command:
+
+```bash
+$ composer require clue/frugal:dev-main
+```
+
+This isn't NPM, so this should only take a moment or two.
+
+## Running
+
+The next step after installing all dependencies is now serve this web application.
+One of the nice properties of this project is that is *runs anywhere* (provided you have PHP installed of course).
+
+For example, you can run the above example using PHP's built-in webserver for
+testing purposes like this:
+
+```bash
+$ php -S 0.0.0.0:8080 app.php
+```
+
+You can now use your favorite webbrowser or command line tool to check your web
+application responds as expected:
+
+```bash
+$ curl -v http://localhost:8080/
+HTTP/1.1 200 OK
+…
+
+Hello wörld!
+```
+
+And that's it already, you can now stop your timer.
+If you've made it this far, you should have an understanding why X is so exciting.
+As a next step, we would recommend checking out the [best practices](../../best-practices/) in order to deploy this to production.
+
+Happy hacking!
diff --git a/docs/integrations/authentication.md b/docs/integrations/authentication.md
new file mode 100644
index 0000000..1fac8f2
--- /dev/null
+++ b/docs/integrations/authentication.md
@@ -0,0 +1,13 @@
+# Authentication
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* HTTP Basic auth easy to implement
+* Implementation as HTTP [middleware](../api/middleware.md) recommended
+* JWT and OAuth possible
+* Handling credentials application-specific, may take advantage of [database](database.md)
+* See also [sessions](sessions.md)
diff --git a/docs/integrations/database.md b/docs/integrations/database.md
new file mode 100644
index 0000000..a0df390
--- /dev/null
+++ b/docs/integrations/database.md
@@ -0,0 +1,17 @@
+# Database
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* Async APIs with [Promises](../async/promises.md)
+* Avoid using blocking PDO, Doctrine and family
+* Major database vendors supported already
+ * [MySQL](https://github.com/friends-of-reactphp/mysql)
+ * [Postgres](https://github.com/voryx/PgAsync)
+ * [SQLite](https://github.com/clue/reactphp-sqlite)
+ * [Redis](https://github.com/clue/reactphp-redis)
+ * [ClickHouse](https://github.com/clue/reactphp-clickhouse)
+* Future DBAL and ORM
diff --git a/docs/integrations/filesystem.md b/docs/integrations/filesystem.md
new file mode 100644
index 0000000..3d949df
--- /dev/null
+++ b/docs/integrations/filesystem.md
@@ -0,0 +1,14 @@
+# Filesystem
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* Async APIs with [Promises](../async/promises.md)
+* Avoid using blocking `fopen()`, `file_get_contents()` and family
+* Few blocking calls *can* be acceptable
+* See [reactphp/filesystem](https://github.com/reactphp/filesystem) for filesystem prototypepq
+* Avoid blocking filesystem by using [child process](child-processes.md)
+* See [clue/reactphp-s3](https://github.com/clue/reactphp-s3) for async S3 filesystem API (supporting Amazon S3, Ceph, MiniIO, DigitalOcean Spaces and others)
diff --git a/docs/integrations/queueing.md b/docs/integrations/queueing.md
new file mode 100644
index 0000000..b4e3437
--- /dev/null
+++ b/docs/integrations/queueing.md
@@ -0,0 +1,14 @@
+# Queueing
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* Common requirement to offload work from frontend to background workers
+* Major queue vendors supported already
+ * [BunnyPHP](https://github.com/jakubkulhan/bunny) for AMQP (RabbitMQ)
+ * [Redis](https://github.com/clue/reactphp-redis) with blocking lists and streams
+ * Experimental [STOMP](https://github.com/friends-of-reactphp/stomp) support for RabbitMQ, Apollo, ActiveMQ, etc.
+* Future optionally built-in queueing support with no external dependencies, but swappable
diff --git a/docs/integrations/sessions.md b/docs/integrations/sessions.md
new file mode 100644
index 0000000..6ebd446
--- /dev/null
+++ b/docs/integrations/sessions.md
@@ -0,0 +1,12 @@
+# Sessions
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* Session handling common requirement and not hard to implement
+* Implementation as HTTP [middleware](../api/middleware.md) recommended
+* Handling credentials application-specific, may take advantage of [database](database.md)
+* See also [authentication](authentication.md)
diff --git a/docs/integrations/templates.md b/docs/integrations/templates.md
new file mode 100644
index 0000000..9bc16a2
--- /dev/null
+++ b/docs/integrations/templates.md
@@ -0,0 +1,14 @@
+# Templates
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* Very common requirement, especially for [HTML pages](../api/response.md#html)
+* Any template language possible
+ * [Twig](https://twig.symfony.com/)
+ * [Handlebars](https://github.com/salesforce/handlebars-php)
+ * [Mustache](https://github.com/bobthecow/mustache.php)
+* Template files often loaded from [filesystem](filesystem.md) (avoid blocking)
diff --git a/docs/more/architecture.md b/docs/more/architecture.md
new file mode 100644
index 0000000..5fcf73f
--- /dev/null
+++ b/docs/more/architecture.md
@@ -0,0 +1,13 @@
+# Architecture
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* HTTP request response semantics
+* PHP runs everywhere
+* shared nothing execution model (optional)
+* ReactPHP (long-running optional)
+* Async PHP
diff --git a/docs/more/community.md b/docs/more/community.md
new file mode 100644
index 0000000..0a32754
--- /dev/null
+++ b/docs/more/community.md
@@ -0,0 +1,20 @@
+# Community
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+Framework X will be released as open-source under the permissive MIT license.
+This means it will be free as in free speech and as in free beer.
+
+We believe in open source and made a conscious decision to take this path.
+Being open-source means we can foster a community to focus on building the best possible framework together.
+Framework X builds on top of existing open-source projects and we want to give back to this community of awesome engineers and developers.
+Being open to outside contributions means we can guarantee interoperability with a vivid ecosystem and ensure the longevity of the project.
+
+* Twitter [@x_framework](https://twitter.com/x_framework)
+* GitHub discussions
+* Support chat
+* GitHub sponsors
diff --git a/docs/more/philosophy.md b/docs/more/philosophy.md
new file mode 100644
index 0000000..62ce5cd
--- /dev/null
+++ b/docs/more/philosophy.md
@@ -0,0 +1,16 @@
+# Our philosophy
+
+> ⚠️ **Documentation still under construction**
+>
+> You're seeing an early draft of the documentation that is still in the works.
+> Give feedback to help us prioritize.
+> We also welcome [contributors](../more/community.md) to help out!
+
+* Motto: make easy things easy & hard things possible
+* From quick prototyping (RAD) to production environment in hours
+* Batteries included, but swappable
+* Reuse where applicable, but accept some duplication
+* Long-term support (LTS) and careful upgrade paths
+* Promote best practices, but don't enfore certain style
+* Runs anywhere
+* Open and inclusive [community](community.md)
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..4dbb56f
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,46 @@
+site_name: Documentation
+site_dir: build/docs
+extra:
+ homepage: ../
+
+theme:
+ name: material
+ features:
+ - navigation.sections
+
+markdown_extensions:
+ - pymdownx.highlight
+ - pymdownx.superfences
+ - toc:
+ permalink: true
+
+nav:
+ - Getting Started:
+# - "Why?": ""
+ - getting-started/quickstart.md
+ - Best Practices:
+ - "Controller classes": best-practices/controllers.md
+ - best-practices/testing.md
+ - best-practices/deployment.md
+ - API:
+ - api/app.md
+ - api/middleware.md
+ - api/request.md
+ - api/response.md
+ - Async:
+ - async/promises.md
+ - async/coroutines.md
+ - async/fibers.md
+ - async/streaming.md
+ - "Child processes": async/child-processes.md
+ - Integrations:
+ - integrations/database.md
+ - integrations/filesystem.md
+ - integrations/authentication.md
+ - integrations/sessions.md
+ - integrations/templates.md
+ - integrations/queueing.md
+ - More:
+ - more/philosophy.md
+ - more/architecture.md
+ - more/community.md