Giter Site home page Giter Site logo

stardub12 / ac3.2-server-side-swift-vapor-iii Goto Github PK

View Code? Open in Web Editor NEW

This project forked from joinpursuit/ac3.2-server-side-swift-vapor-iii

0.0 2.0 0.0 576 KB

Dependencies, Databases and Deployment

License: MIT License

Shell 53.39% Swift 46.61%

ac3.2-server-side-swift-vapor-iii's Introduction

Server Side Swift w/ Vapor - Part III

An intro to Server Side Swift using Vapor

Readings:

  1. RESTful Controllers - RW
  2. Server Side Swift with Vapor: Persisting Models - RW
  3. Server Side Swift with Vapor: CRUD Database Options - RW

Helpful Resources:

  1. Vapor - Big Nerd Ranch

API Reference:

  1. Vapor API Reference

RESTful Controllers (Intermediate Routing)

You may recall that the RESTful API's we've consumed in the pass all generally had the same set of endpoints to hit: one for getting a single object, another for creating a new object, another for updating it, another for deleting, etc.. Well, Vapor knows that Swift developers would want to create a similar structure for their code. And such, they added the ResourceRepresentable protocol. The ResourceRepresentable protocol more easily allows you to handle common RESTful requests.

Create a new file, CatRESTController.swift

The code starts out simple enough, as ResourceRepresentable only has one required method makeResource() -> Resource

final class CatRESTController: ResourceRepresentable {
  func makeResource() -> Resource<Cat> {
    
    return Resource()
  }
}

Though if we try to build at this point, we're met with an error:

Our Model... Almost

In order to set up this RESTful controller, we're going to need to implement the Model protocol on our Cat object. Which fortunately, is not all that difficult. We just have to implement two functions init(row:) throws and makeRow() throws -> Row. The basics to the implementation are very straightforward so here is the code:

final class Cat: Model, NodeInitializable, NodeRepresentable {

  // ... all of the other code ...

  // MARK: Persistance Storage / Fluent
  func makeRow() throws -> Row {
    var row = Row()
    try row.set("name", self.name)
    try row.set("breed", self.breed)
    try row.set("snack", self.snack)
    return row
  }
  
  init(row: Row) throws {
    self.name = try row.get("name")
    self.breed = try row.get("breed")
    self.snack = try row.get("snack")
  }

In order for us to not have to worry about setting up the actual database and tables, we have Cat conform to one more protocol, Preparation:

// MARK: Preparation
extension Cat: Preparation {
  static func prepare(_ database: Database) throws {
    
    try database.create(Cat.self) { creator in
      creator.id()
      creator.string("name")
      creator.string("breed")
      creator.string("snack")
    }
  }
  
  static func revert(_ database: Database) throws {
    try database.delete(Cat.self)
  }
}

Id encourage you to look at the documentation for the protocols and the Creator object, along with the Database object for a deeper understanding of what is happening. But it suffices for now to look at the code and to simply understand that in the prepare(_:) method, we're creating a database table with 4 columns: id, name, breed, snack. The revert function is used to clear the database should we want to clear everything one.

Note the function itself isn't called from Xcode, it's the code that gets run from the command line as vapor build followed by vapor run prepare --revert

Back in our CatRESTController we can now go back and start filling in our functions. We're going to start with just two: one to display all of our cats, and one to add a single Cat to our database. Just like before, we're tasked with creating functions that match the type (Request) throws -> ResponseRepresentable.

Index

Thanks to conforming to Model, we're going to get a lot of database querying for free. For example, our index function whose goal is to display a list of all Cat objects is going to have a very simple execution statement:

final class CatRESTController: ResourceRepresentable {
  
  func index(_ request: Request) throws -> ResponseRepresentable {
    return try Cat.all().makeJSON()
  }
  
  // The only way for your resource to work here, is if it conforms to the Model protocol
  func makeResource() -> Resource<Cat> {
    return Resource(index:  index)
  }

}

That .all() function is a result of having Cat conform to Model which is a protocol declared in the Fluent module. To get a representation that we can use, it's as simple as calling makeJSON() and returning the result.

Remember Fluent?

We also need to fill out the makeResource function by having it return a Resource object with a single handler for the index call.

Registering our Resource

Just like before, we need to let our Vapor server where this CatRESTController is in relation to our base URL. Though instead of specifying the endpoint name by calling .get() and passing a string, we need to set our CatRESTController as a resource because it is now RESTful. In our main.swift we just add:

try drop.resource("catREST", CatRESTController.self)

This effectively creates a /catREST endpoint with a number of (potential) HTTP requests. At this point, we can test this in Postman and navigate to localhost:8080/catREST to observe an empty array to verify that our URL is working.

Creating Cat

We should try adding a new Cat to our database to see that everything is working well. So, lets define a new function called create that will create a new Cat and save it. Take a moment now to try to fill the basic functionality before looking below:

	func create(_ request: Request) throws -> ResponseRepresentable {
    	
    	// 1.
    	guard
      		let catName = request.json?["cat_name"]?.string,
      		let catBreed = request.json?["cat_breed"]?.string,
      		let catSnack = request.json?["cat_snack"]?.string 
      	else {
        	throw Abort.badRequest
    	}
    	
    	// 2.
    	let newCat = Cat(name: catName, breed: catBreed, snack: catSnack)
    	try newCat.save()

    	// 3.
    	return try JSON(node: ["success": true, "cat" : newCat])
  	}
  1. The first step involves inspecting the JSON of the Request object for the key/value pairs needed to define the properties of a Cat, otherwise we throw an error if the request is malformed.
  2. Having guaranteed our necessary params, we create an instance of Cat and use our new, and free, method save() to attempt to store the Cat to our database.
  3. Lastly, we return a response JSON to let us know that all went well with storing.

We need to add this handler to the endpoint, so update makeResource to include this new function:

	func makeResource() -> Resource<Cat> {
    	return Resource(index:  index,
        	            store: create
  	}

And the final bit of code we're going to add is to Config+Setup.swift in order to ensure that our database schema is set up when the app is run:

	/// Add all models that should have their
    /// schemas prepared before the app boots
    private func setupPreparations() throws {
        preparations += [Post.self]
        preparations += [Cat.self] // add this line
    }

Now, let's try to add a new Cat by making a POST request to /catREST:

Verify our success:

And lastly, check for its existance by making a GET request to /catREST:

You might be wondering: Why is it that we don't call /catREST/index or /catREST/create? Well, that's just how it works after you've conformed to the ResourceRepresentable protocol: you requests are handled differently according to web conventions. If you're curious about this further, look at the Resource class in the Vapor module.

Show

OK let's do just one more for the show function which is used to retrieve a single resource object based on its unique identifier, which for our Cats is an id that gets generated automatically for us. The request will be a GET and endpoint will look like /catREST/:id where the id is an Int parameter corresponding to the appropriate cat Cat.

Implementing show is pretty easy, but one thing we need to take account of is that the function type for show expects a type Item which happens to be a typealias defined in Resource as (Request, Model) throws -> ResponseRepresentable. What the type tells us, is that in addition to the Request parameter, we're going to have a second parameter of type Model. Simply put, that Model object is going to correspond to the Cat that we should be passing back:

  // show expects a function of type (Request, Model) throws -> ResponseRepresentable
  func show(_ request: Request, cat: Cat) throws -> ResponseRepresentable {
    return cat
  }

  // let's not forget to update this as well 
  func makeResource() -> Resource<Cat> {
    return Resource(index:  index,
                    store: create,
                    show: show)
  }

Advanced: Why is it that we can replace Model with a type Cat in our show function? Well, if you looked at the ResourceRepresentable protocol you would have noticed that it has an associatedtype of Model: Parameterizable. We conformed our Cat object to Model which automatically is made Parameterizable by Vapor. ResourceRepresentable has that single function that needs implementing: func makeResource() -> Resource<Model>. When we implemented our CatRESTController with func makeResource() -> Resource<Cat> we implicitly stated that our class's associatedtype would be Cat, therefor we can rewrite the Item typealias to use our class's associatedtype Model as being Cat.

Rerun the project and check out /catREST first. Notice anything odd? (hint: where did our cats go?)

NOTE: Persistance Drivers

The database library, Fluent, that is built in with Vapor is an in-memory datastore by default. This means that the database is created, used and destroyed within the lifetime of the app's run. We're probably going to want to do some sort of permanent storage though!! We're going to return to this shortly, but for now, just make a few POST requests to fill up our db with something we can work with.

After you've added in a few Cats, go ahead and make a GET request to the index to make sure you see them all. And lastly, make a request to catREST/:id to see if you can access individual Cat entries.

Congrats! We have implemented local (semi-)persistant storage.

Installing PostgreSQL

If you can run the psql command in Terminal, you're all set. Otherwise:

$ brew update
$ brew install postgresql

# after installation finishes:

# starts running local postgres
$ postgres -D /usr/local/var/postgres 

# you should open a new terminal tab for this one
$ createdb whoami

# check to make sure the db has been created
$ psql 

# inside the psql repl, these will show you your db's and tables, respectively
# you should see one with your name/whatever the output of whoami is
> \l
> \d

# Other tips:

# This quits the interactive shell
> \q

You may also find it helpful to use an app to manage your databases (highly suggested). One that I've found that works pretty well for our purposes is Postico (Free). The initial setup is simple, just replace my name in the user parameter to whatever whoami outputs in your terminal (probably will be your name).

Postico Config

Postico Main

Adding PostgreSQL to your Project for true permanent storage

Navigate to the Vapor Community page for PostgreSQL. This package is maintained by the open source community so that all may benefit from using this technology. Each package for Vapor is given a list of detailed instructions on the repo's README on how to implement the library. In implementing this package, we're going to have our project backed by PostgreSQL, which is a permanent data storage option, as opposed to Fluent's in-memory option. To do this (as stated on the README) we need:

  1. Add the dependacy to our Package.swift file
    • .Package(url: "https://github.com/vapor/postgresql-provider.git", majorVersion: 2, minor: 0)
  2. Rebuild our project so it has the new dependency
    • $ vapor update / $ vapor xcode -y
  3. Register our provider with our Droplet
    • try drop.addProvider(PostgreSQLProvider.Provider.self)
  4. Update config/fluent.json by changing this key/value "driver": "postgresql" (by default it will be "driver": "memory")
  5. Creating a config/secrets/postgresql.json file (add the basic json from the PostgreSQL link)

If we have all of the above in place, now making POST requests to /catREST should store our data in a SQL database that persists between runs of the app. Let's go ahead and test this out to make sure that this is the case.

Note: You'll see a table named fluent as well -- this is generated by Vapor/Fluent and is used to index/manage your databases. DO NOT directly manipulate this table.

Now for some REST

Great work. We now have the basics of a RESTful API in the works! We can display and add new Cat resources and ensure that the data is persisted, all from a single Swift project. There's now just 2 more things I'd like to cover: displaying our data and hosting our api. But that's for next time.

Future topics:

  1. Displaying our data with Leaf
  2. Pushing to Heroku

ac3.2-server-side-swift-vapor-iii's People

Contributors

spacedrabbit avatar

Watchers

James Cloos avatar Simone avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.