diff --git a/3.0/docs/vapor/content.md b/3.0/docs/vapor/content.md index de225165..217477c4 100644 --- a/3.0/docs/vapor/content.md +++ b/3.0/docs/vapor/content.md @@ -283,6 +283,68 @@ let flags: Flags ... try req.query.encode(flags) ``` +## Dynamic Properties + +One of the most frequently asked questions regarding `Content` is: + +> How do I add a property to just this response? + +The way Vapor 3 handles `Content` is based entirely on `Codable`. At no point (that is publically accessible) is your data in an arbitrary data structure like `[String: Any]` that you can mutate at will. Because of this, all data structures that your app accepts and returns _must_ be statically defined. + +Let's take a look at a common scenario to better understand this. Very often when you are creating a user, there are a couple different data formats required: + +- create: password should be supplied twice to check values match +- internal: you should store a hash not the plaintext password +- public: when listing users, the password hash should not be included + +To do this, you should create three types. + +```swift +// Data required to create a user +struct UserCreate: Content { + var email: String + var password: String + var passwordCheck: String +} + +// Our internal User representation +struct User: Model { + var id: Int? + var email: String + var passwordHash: Data +} + +// Public user representation +struct PublicUser: Content { + var id: Int + var email: String +} + +// Create a router for POST /users +router.post(UserCreate.self, at: "users") { req, userCreate -> PublicUser in + guard userCreate.password == passwordCheck else { /* some error */ } + let hasher = try req.make(/* some hasher */) + let user = try User( + email: userCreate.email, + passwordHash: hasher.hash(userCreate.password) + ) + // save user + return try PublicUser(id: user.requireID(), email: user.email) +} +``` + +For other methods such as `PATCH` and `PUT`, you may want to create additional types to supports the unique semantics. + +### Benefits + +This method may seem a bit verbose at first when compared to dynamic solutions, but it has a number of key advantages: + +- **Statically Typed**: Very little validation is needed on top of what Swift and Codable do automatically. +- **Readability**: No need for Strings and optional chaining when working with Swift types. +- **Maintainable**: Large projects will appreciate having this information separated and clearly stated. +- **Shareable**: Types defining what content your routes accept and return can be used to conform to specifications like OpenAPI or even be shared directly with clients written in Swift. +- **Performance**: Working with native Swift types is much more performant than mutating `[String: Any]` dictionaries. + ## JSON JSON is a very popular encoding format for APIs and the way in which dates, data, floats, etc are encoded is non-standard. Because of this, Vapor makes it easy to use custom [`JSONDecoder`](#fixme)s when you interact with other APIs. diff --git a/3.0/docs/version/upgrading.md b/3.0/docs/version/upgrading.md new file mode 100644 index 00000000..d7719aed --- /dev/null +++ b/3.0/docs/version/upgrading.md @@ -0,0 +1,74 @@ +# Upgrading Versions + +This document provides information about changes between version and tips for migrating your projects. + +## 2.4 to 3.0 + +Vapor 3 has been rewritten from the ground up to be async and event-driven. This release contains the most changes of any previous release (and most likely any future release). + +Because of this, it is recommended that to migrate your projects you start by creating a new, empty template and migrate by copy / pasting code over to the new project. + +We recommend reading the [Getting Started → Hello, world!](../getting-started/hello-world.md) section for Vapor 3 to familiarize yourself with the new APIs. + +### Async + +The biggest change in Vapor 3 is that the framework is now completely asynchronous. When you call methods that need to perform slow work like network requests or disk access instead of blocking they will now return a `Future`. + +Futures are values that may not exist yet, so you cannot interact with them directly. Instead, you must use `map`/`flatMap` to access the values. + +```swift +// vapor 2 +let res = try drop.client.get("http://vapor.codes") +print(res.status) // HTTPStatus +return res.status + +// vapor 3 +let f = try req.client().get("http://vapor.codes").map { res in + print(res.http.status) // HTTPStatus + return res.http.status +} +print(f) // Future +``` + +See [Async → Getting Started](../async/getting-started.md) to learn more. + +### Application & Services + +`Droplet` has been renamed to `Application` and is now a service-container. In Vapor 2, the `Droplet` had stored properties for things you would need during development (like views, hashers, etc). In Vapor 3, this is all done via services. + +While the `Application` _is_ a service-container, you should not use it from your route closures. This is to prevent race conditions since Vapor runs on multiple threads (event loops). Instead, use the `Request` that is supplied to your route closure. This has a _copy_ of all of the application's services for you to use. + +```swift +// vapor 2 +return try drop.view.make("myView") + +// vapor 3 +return try req.make(ViewRenderer.self).render("myView") +// shorthand +return try req.view().render("myView") +``` + +See [Service → Getting Started](../service/getting-started.md) to learn more. + +### Database Connections + +In Vapor 3, database connections are no longer statically accessible. This makes doing things like transactions and connection pooling much more predictable and performant. + +In order to create a `QueryBuilder` in Fluent 3, you will need access to something `DatabaseConnectable`. Most often you can just use the incoming `Request`, but you can also create connections manually if you need. + +```swift +// vapor 2 +User.makeQuery().all() + +// vapor 3 +User.query(on: req).all() +``` + +See [DatabaseKit → Getting Started](../database-kit/getting-started.md) to learn more. + +### Work in progress + +This migration guide is a work in progress. Please feel free to add any migration tips here by submitting a PR. + +Join the [#upgrading-to-3](https://discordapp.com/invite/BnXmVGA) in Vapor's team chat to ask questions and get help in real time. + diff --git a/3.0/mkdocs.yml b/3.0/mkdocs.yml index 7721b9fa..e96e5753 100644 --- a/3.0/mkdocs.yml +++ b/3.0/mkdocs.yml @@ -116,6 +116,7 @@ pages: - '1.5': 'version/1_5.md' - '2.0': 'version/2_0.md' - '3.0': 'version/3_0.md' + - 'Upgrading': 'version/upgrading.md' - 'Support': 'version/support.md'