Swift 3が正式リリースされてから、触っていませんでした。 久々に見てみると、ドキュメントや周辺ツールがかなり充実していました。

インストール

$ curl -sL check.vapor.sh | bash
✅  Compatible

# これをやらないとInstalling...から進みませんでした
$ brew uninstall vapor
Uninstalling /usr/local/Cellar/vapor/0.4.1... (4 files, 17.7K)

$ curl -sL toolbox.vapor.sh | bash
✅  Compatible
Downloading...
Compiling...
Installing...
Vapor Toolbox v1.0.3 Installed
Use vapor --help and vapor <command> --help to learn more.

$ vapor --help
Usage: vapor <new|build|run|fetch|clean|test|xcode|version|self|heroku|docker>
Join our Slack if you have questions, need help,
or want to contribute: http://vapor.team

プロジェクト作成 

$ vapor new LTNSVapor
$ cd LTNSVapor/
$ vapor xcode -y

App Schemeを選択して実行すると、http://localhost:8080/でアクセスできるようになっています。

Postsを使えるように

この状態で、http://localhost:8080/posts にアクセスすると以下のようなエラーになります。

Uncaught Error: EntityError.noDatabase. Use middleware to catch this error and provide a better response. Otherwise, a 500 error page will be returned in the production environment.

vapor/AuthTests.swift at master · vapor/vapor を参考に、Memoryにデータを保存するようにしました。

let database = Database(MemoryDriver())
drop.database = database
drop.preparations.append(Post.self)

また、Post.swiftに以下の記述があり、これを消さないとshowのURLにアクセスするたびに勝手にモデルが作成されてしまうので、コメントアウトします。

extension Post {
    /**
        This will automatically fetch from database, using example here to load
        automatically for example. Remove on real models.
    */
    public convenience init?(from string: String) throws {
        self.init(content: string)
    }
}

これでPostsのリソースが動くようになりました。 確認のため、welcome.leafに以下のようなFormを書きました。

    <div id="posts"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.38/browser.min.js"></script>
    <script type="text/babel">
        class PostList extends React.Component {
            constructor(props) {
                super(props)
                this.state = {
                    posts: [],
                    content: '',
                }
            }
            componentDidMount() {
                this.load()
            }
            load() {
                fetch('/posts')
                .then(response => response.json())
                .then(json => {
                    this.setState({ posts: json })
                })
            }
            handleSubmit(event) {
                event.preventDefault()
                const headers = new Headers()
                headers.append('Content-Type', 'application/json')
                fetch('/posts', {
                    method: 'POST',
                    headers: headers,
                    body: JSON.stringify({ content: this.state.content }),
                })
                .then(() => this.load())
                this.setState({ content: '' })
            }
            handleChange(event) {
                this.setState({ content: event.target.value })
            }
            handleDelete(id) {
                fetch('/posts/' + id, {
                    method: 'DELETE',
                })
                .then(() => this.load())
            }
            render() {
                return (
                    <ul>
                        {this.state.posts.map(post =>
                            <li>
                                <a href={ `/posts/${post.id}` }>{ post.content }</a>
                                &nbsp;
                                <a onClick={ () => this.handleDelete(post.id) } style=>&times;</a>
                            </li>
                        )}
                        <li>
                            <form onSubmit={ this.handleSubmit.bind(this) }>
                                <input type="text" value={ this.state.content } onChange={ this.handleChange.bind(this) } />
                                <input type="submit" value="Add" />
                            </form>
                        </li>
                    </ul>
                )
            }
        }

        ReactDOM.render(<PostList />, document.getElementById('posts'))
    </script>

以前よりもっと手軽に使えるようになった感じでした。 Herokuへのデプロイも簡単にできるようなので、何か作ったら今度はHerokuにあげてみようと思います。

参考

ソースコード

https://github.com/tnantoka/swift-a-week/tree/gh-pages/works/LTNSVapor