New to Swift and Vapor. Returning data from MySQL


(Ben Cousins) #1

So, as per the title, I'm new to Vapor and Swift in general, so I've been battling for about a month now trying to get things to return from an existing database.

We're using Vapor 3.

I'm coming from PHP, and of course, we have a database already. For a range of reasons, Fluent's migration system isn't an option for us. We don't want it to be screwing with the database when we build.

With that said, I've set up a model, and have been attempting to run queries on it. It builds successfully, and then gives the error:

{"error":true,"reason":"Model.defaultDatabase is required to use request as DatabaseConnectable."}

Our configure.swift looks like this.

import Vapor
import Leaf
import MySQL

public func configure(
    _ config: inout Config,
    _ env: inout Environment,
    _ services: inout Services
) throws {

// Register routes to the router
let router = EngineRouter.default()
try routes(router)
services.register(router, as: Router.self)

let myService = NIOServerConfig.default(port: 8080)
services.register(myService)

try services.register(LeafProvider())
try services.register(MySQLProvider())

config.prefer(LeafRenderer.self, for: ViewRenderer.self)

    
/// Register custom MySQL Config
let mysqlConfig = MySQLDatabaseConfig(
    hostname: "<ip>",
    port: 3306,
    username: "<user>",
    password: "<password>",
    database: "<database>"
)
services.register(mysqlConfig)

}

Our model is written to the standard expressed here: https://docs.vapor.codes/3.0/mysql/fluent/#model

And our controller has this:

import Vapor
import FluentMySQL

final class NewsController {

func index(_ req: Request) throws -> Future<View> {
    let news = News.query(on: req).all()
	return try req.view().render("newsIndex", news)
}
}

Any idea where I'm going wrong?


(Tim) #2

@s3_gunzel working with an existing database is still a little bit in flux as Fluent hasn't made it out of beta, however the following should work (though there may be syntactic sugar added in the future.

In your model, don't define it as a Migration - that will cause issues with the existing data.

In configure.swift you need to define an actual database instead of just the configuration:

// Configure a SQLite database
var databases = DatabaseConfig()
let mysqlConfig = MySQLDatabaseConfig(hostname: "<ip>", port: 3306, username: "<user>", password: "<password>", database: "<database>")
let database = MySQLDatabase(config: mysqlConfig)
databases.add(database: database, as: .mysql)
services.register(databases)

This sets up the .mysql database in Vapor. Then at the bottom of configure.swift you can link that to your model with:

News.defaultDatabase = .mysql

This is what a migration normally does but since you don't want to create the table and have errors throwing from there. This links up the queries so that when you make a query on News it knows which database to use.


(Tim) #3

PS - I highly recommend joining Vapor's Discord server - there are a ton of really helpful people there who will be able to answer questions quickly so you don't get stuck for a month!


(Ben Cousins) #4

And then I have to do that for each model, correct? And this would be all as an

import FluentMySQL

wouldn't it?

Edit: That's worked, by the way. Thanks.


(Tim) #5

Yep importing FluentMySQL will expose the .mysql database


(Ben Cousins) #6

Right so I get a response back from /mysql-version now, as expected. I also did before, so no change there.

But now, when I go to load "/" - as per the routing, which thows NewsController.index, I get this:

{"error":true,"reason":"Cannot decode Int from MySQLData: string("")."}

It's not pulling data from the database, still. It's not seeing the table that's set up.


(Tim) #7

That looks like the data it's getting from the query doesn't match with the model.

If you do databases.enableLogging(on: .mysql) before services.register(databases) that will turn on the queries it's making to the database.

The error usually occurs when your DB is set up with a column type of String but your model has Int as the property type


(Ben Cousins) #8

I was just in the process of setting up serverside logging. It's good to know there's a way to do that in vapor, so I don't have to do it serverside.

I've made a stupid mistake somewhere. I'll keep looking into it. Thanks :)


(Ben Cousins) #9

New error today.

Sorry, still getting the hang of this.

News does not conform to TemplateDataRepresentable.

Is that the model, or the set of data - and how do I make it conform to TemplateDataRepresentable? There's no documentation (which I can find) for what needs to be returned to conform.

Another question, I want to print(news) - what future do I need to throw to do that?


(Tim) #10

You can use map to unwrap the future for printing.

As for the error, it should work but you might need to wrap the query data in a separate struct. E.g. https://github.com/raywenderlich/vapor-til/blob/master/Sources/App/Controllers/WebsiteController.swift#L248


(Ben Cousins) #11

Thank you SO much for your help. I have now got things coming out from the database in Leaf, exactly what I wanted. You're a godsend, @0xTim