diff --git a/couscous.yml b/couscous.yml index 6fb81c46..c840f026 100644 --- a/couscous.yml +++ b/couscous.yml @@ -29,6 +29,12 @@ menu: guide: name: Guide items: + guide-droplet: + text: Droplet + relativeUrl: guide/droplet.html + guide-folder-structure: + text: Folder Structure + relativeUrl: guide/folder-structure.html guide-routing: text: Routing relativeUrl: guide/routing.html diff --git a/getting-started/install-swift-3.md b/getting-started/install-swift-3.md index 3e9fee1e..5ca40cd2 100644 --- a/getting-started/install-swift-3.md +++ b/getting-started/install-swift-3.md @@ -28,7 +28,7 @@ export PATH="$SWIFTENV_ROOT/bin:$PATH" eval "$(swiftenv init -)" ``` -Note: macOS uses `~/.bash_profile` and Ubuntu uses `~/.bashrc` +> Note: macOS uses `~/.bash_profile` and Ubuntu uses `~/.bashrc` ### Verify diff --git a/getting-started/install-toolbox.md b/getting-started/install-toolbox.md index 82ba44c2..91e486d7 100644 --- a/getting-started/install-toolbox.md +++ b/getting-started/install-toolbox.md @@ -14,7 +14,7 @@ Run the following script to install the [Toolbox](https://github.com/qutheory/to curl -sL toolbox.qutheory.io | bash ``` -Note: You must have the correct version of Swift 3 installed. +> Note: You must have the correct version of Swift 3 installed. ## Verify diff --git a/guide/droplet.md b/guide/droplet.md new file mode 100644 index 00000000..9ca872b0 --- /dev/null +++ b/guide/droplet.md @@ -0,0 +1,82 @@ +--- +currentMenu: guide-droplet +--- + +# Droplet + +The `Droplet` is a service container that gives you access to many of Vapor's facilities. It is responsible for registering routes, starting the server, appending middleware, and more. + +## Initialization + +As you have probably already seen, the only thing required to create an instance of `Droplet` is to import Vapor. + +```swift +import Vapor + +let drop = Droplet() + +// your magic here + +drop.start() +``` + +Creation of the `Droplet` normally happens in the `main.swift` file. + +## Environment + +The `environment` property contains the current environment your application is running in. Usually development, testing, or production. + +```swift +if drop.config.environment == .production { + ... +} +``` + +The environment affects [Config](config.md) and [Logging](log.md). The environment is `development` by default. To change it, pass the `--env=` flag as an argument. + +```sh +vapor run serve --env=production +``` + +If you are in Xcode, you can pass arguments through the scheme editor. + +> Note: Debug logs can reduce the number of requests your application can handle per second. Enabling the production environment can improve performance. + +## Working Directory + +The `workDir` property contains a path to the current working directory of the application relative to where it was started. By default, this property assumes you started the Droplet from its root directory. + +```swift +drop.workDir // "/var/www/my-project/" +``` + +You can override the working directory through the `Droplet`'s initializer, or by passing the `--workdir` argument. + +```sh +vapor run serve --workdir="/var/www/my-project" +``` + +## Initialization + +The `Droplet` has several customizable properties. + +Most plugins for Vapor come with a [Provider](providers.md), these take care of configuration details for you. + +```swift +Droplet( + arguments: [String]?, + workDir workDirProvided: String?, + config configProvided: Config?, + localization localizationProvided: Localization?, + server: ServerProtocol.Type?, + sessions: Sessions?, + hash: Hash?, + console: ConsoleProtocol?, + log: Log?, + client: ClientProtocol.Type?, + database: Database?, + preparations: [Preparation.Type], + providers: [Provider.Type], + initializedProviders: [Provider] +) +``` diff --git a/guide/folder-structure.md b/guide/folder-structure.md new file mode 100644 index 00000000..319893e7 --- /dev/null +++ b/guide/folder-structure.md @@ -0,0 +1,88 @@ +--- +currentMenu: guide-folder-structure +--- + +# Folder Structure + +The first step to creating an awesome application is knowing where things are. If you created your project using the [Toolbox](../getting-started/toolbox.md) or from a template, you will already have the folder structure created. + +If you are making a Vapor application from scratch, this will show you exactly how to set it up. + +## Minimum Folder Structure + +We recommend putting all of your Swift code inside of the `App/` folder. This will allow you to create subfolders in `App/` to organize your models and resources. + +This works best with the Swift package manager's restrictions on how packages should be structured. + +``` +. +├── App +│ └── main.swift +├── Public +└── Package.swift +``` + +The `Public` folder is where all publicly accessible files should go. This folder will be automatically checked every time a URL is requested that is not found in your routes. + +> Note: The `FileMiddleware` is responsible for accessing files from the `Public` folder. + +## Models + +The `Models` folder is a recommendation of where you can put your database and other models, following the MVC pattern. + +``` +. +├── App +. └── Models +. └── User.swift +``` + +## Controllers + +The `Controllers` folder is a recommendation of where you can put your route controllers, following the MVC pattern. + +``` +. +├── App +. └── Controllers +. └── UserController.swift +``` + +## Views + +The `Views` folder in `Resources` is where Vapor will look when you render views. + +``` +. +├── App +└── Resources + └── Views + └── user.html +``` + +The following code would load the `user.html` file. + +```swift +drop.view("user.html") +``` + +## Config + +Vapor has a sophisticated configuration system that involves a hierarchy of configuration importance. + +``` +. +├── App +└── Config + └── app.json // default app.json + └── development + └── app.json // overrides app.json when in development environment + └── production + └── app.json // overrides app.json when in production environment + └── secrets + └── app.json // overrides app.json in all environments, ignored by git +``` + +`.json` files are structured in the `Config` folder as shown above. The configuration will be applied dependant on where the `.json` file exists in the hierarchy. Learn more in [Config](config.md). + +Learn about changing environments (the `--env=` flag) in the [Droplet](droplet.md) section. diff --git a/guide/routing.md b/guide/routing.md index ba9d333f..8429212c 100644 --- a/guide/routing.md +++ b/guide/routing.md @@ -4,8 +4,163 @@ currentMenu: guide-routing # Routing -This is how routing works. +Routes in Vapor can be defined in any file that has access to your instance of `Droplet`. This is usually in the `main.swift` file. + +## Basic + +The most basic route includes a method, path, and closure. ```swift -let drop = Droplet() -``` \ No newline at end of file +drop.get("welcome") { request in + return "Hello" +} +``` + +The standard HTTP methods are available including `get`, `post`, `put`, `patch`, `delete`, and `options`. + +You can also use `any` to match all methods. + +## Request + +The first parameter passed into your route closure is an instance of [Request](request.md). This contains the method, URI, body, and more. + +```swift +let method = request.method +``` + +When you add a parameter type, like `String.self`, the closure will be required to contain another input variable. This variable will be the same type. In this case, `String`. + +## JSON + +To respond with JSON, simply wrap your data structure with `JSON(node: )` + +```swift +drop.get("version") { request in + return try JSON(node: ["version": "1.0"]) +} +``` + +## Response Representable + +All routing closures can return a [ResponseRepresentable](response.md) data structure. By default, Strings and JSON conform to this protocol, but you can add your own. + +```swift +public protocol ResponseRepresentable { + func makeResponse() throws -> Response +} +``` + +## Parameters + +Parameters are described by passing the type of data you would like to receive. + +```swift +drop.get("hello", String.self) { request, name in + return "Hello \(name)" +} +``` + +## String Initializable + +Any type that conforms to `StringInitializable` can be used as a parameter. By default, `String` and `Int` conform to this protocol, but you can add your own. + +```swift +struct User: StringInitializable { + ... +} + +app.get("users", User.self) { request, user in + return "Hello \(user.name)" +} +``` + +Using Swift extensions, you can extend your existing types to support this behavior. + +```swift +extension User: StringInitializable { + init?(from string: String) throws { + guard let int = Int(string) else { + return nil //Will Abort.InvalidRequest + } + + guard let user = User.find(int) else { + throw UserError.NotFound + } + + self = user + } +} +``` + +You can throw your own errors or return `nil` to throw the default error. + +## Groups + +Prefix a group of routes with a common string, host, or middleware using `group` and `grouped`. + +### Group + +Group (without the "ed" at the end) takes a closure that is passed a `GroupBuilder`. + +```swift +drop.group("v1") { v1 in + v1.get("users") { request in + // get the users + } +} +``` + +### Grouped + +Grouped returns a `GroupBuilder` that you can pass around. + +```swift +let v1 = drop.grouped("v1") +v1.get("users") { request in + // get the users +} +``` + +### Middleware + +You can add middleware to a group of routes. This is especially useful for authentication. + +```swift +drop.group(AuthMiddleware()) { authorized in + authorized.get("token") { request in + // has been authorized + } +} +``` + +### Host + +You can limit the host for a group of routes. + +``` +drop.group(host: "qutheory.io") { qt + qt.get { request in + // only responds to requests to qutheory.io + } +} +``` + +### Chaining + +Groups can be chained together. + +```swift +drop.grouped(host: "qutheory.io").grouped(AuthMiddleware()).group("v1") { authedSecureV1 in + // add routes here +} +``` + +## Custom Routing + +Vapor still supports traditional routing for custom use-cases or long URLs. + +```swift +drop.get("users/:user_id") { request in + request.parameters["user_id"] // String? +} +``` diff --git a/template/default.twig b/template/default.twig index ee2bc102..f9e47ab8 100644 --- a/template/default.twig +++ b/template/default.twig @@ -57,7 +57,7 @@
- ✏️ Edit + ✎ Edit on GitHub {{ content | raw }}
diff --git a/template/styles/main.css b/template/styles/main.css index 8694ccb3..20827093 100644 --- a/template/styles/main.css +++ b/template/styles/main.css @@ -20,7 +20,7 @@ a:hover { } h1 { - font-size: 48px; + font-size: 42px; font-weight: 200; } @@ -112,7 +112,7 @@ nav { left: 0; top: 0; bottom: 0; - width: 200px; + width: 216px; z-index: 8; padding-top: 96px; @@ -127,6 +127,7 @@ nav a { font-size: 14px; display: block; color: #777; + font-size: 12px; height: 24px; } @@ -141,9 +142,9 @@ nav .active a { nav h3 { text-transform: uppercase; - color: #999; + color: #bbb; font-size: 14px; - margin-bottom: 4px; + margin-bottom: 6px; font-weight: 400; } @@ -154,7 +155,7 @@ nav ul, nav ul li { } nav ul { - margin-bottom: 24px; + margin-bottom: 18px; } div.scroll { @@ -169,7 +170,7 @@ main { font-weight: 200; padding: 22px; padding-top: 110px; - padding-left: 222px; + padding-left: 240px; position: absolute; position: relative; @@ -191,15 +192,39 @@ main a.edit { border-bottom: none; } +main h1 { + margin-bottom: 12px; +} + main h2 { margin-top: 24px; - border-bottom: 1px dotted rgba(0, 0, 0, 0.10); margin-bottom: 12px; + border-bottom: 1px dotted rgba(0, 0, 0, 0.10); +} + +main h3 { + margin-top: 18px; } main p { margin-top: 0; - margin-bottom: 6px; + margin-bottom: 12px; + line-height: 1.75; +} + +main blockquote { + background: rgba(247, 202, 201, 0.2); + margin-top: 18px; + margin-bottom: 18px; + margin-left: 0; + padding: 6px; + padding-left: 12px; + border-left: 5px solid #f6cfcf; +} + +main blockquote p { + margin: 0; + padding: 0; } pre, code { @@ -209,10 +234,11 @@ pre, code { p code { background: #fbfbfb; - border-radius: 10px; + border-radius: 5px; padding: 3px; display: inline-block; color: #92a0b9; + box-shadow: 0 1px 0px 0px rgba(0, 0, 0, 0.1); } pre {