Play Framework 2.8, Scala, Slick, MySQL, and sbt

Play Framework 2.8, Scala, Slick, MySQL, and sbt

The basics of building a Scala/Play Framework

Introduction

For demonstrate how to build an asynchronous & non-blocking Play framework. it will take all have being simplicity. create a very basic simple CRUD that allows to perform and presentation the very basic of CRUD operations for the data stored in a Mysql database.

Prerequisite

The post is accessible for beginners. However, Scala and Play framework fundamentals are required. Before going any further, please make sure that you have the following tools installed on your local machine.

Architecture of Play Framework

The typical architecture of a Play Framework web application. Here, some components are mandatory for all applications, such as the routes file, Controller, View Templates, and so on; some are optional:

Following steps in any Play Framework Web Application for Observer:

  1. The users access a Play Web Application, using the application URL and a web browser.

  2. User sends a HTTP request to a Play Web Application, such as /route2 for request reaches the Play Framework's Routing component (the default route filename is routes).

Create a Play Framework project

Create a Play Framework 2.8 project from their sbt/Giter8 template:

sbt new playframework/play-scala-seed.g8

MySQL database

  • Using XAMPP on macOS

  • Username: root

  • Password: root

  • Port: 8080

Create the database and table at the MySQL command prompt:

# STEP 1:
CREATE USER 'play'@'localhost' IDENTIFIED BY 'play';

# STEP 2:
GRANT ALL PRIVILEGES
ON play101.*
TO 'play'@'localhost'
WITH GRANT OPTION;

# USE the db
use play101;

# CREATE the table(s)
create table stocks (
    id int auto_increment not null,
    symbol varchar(10) not null,
    company varchar(32) not null,
    primary key (id),
    constraint unique index idx_stock_unique (symbol)
);

# INSERT some data
INSERT INTO stocks (symbol, company) VALUES ('AAPL', 'Apple');
INSERT INTO stocks (symbol, company) VALUES ('GOOG', 'Google');

That’s not a very practical table, but it’s good enough for a simple Play/MySQL example.

build.sbt

My build.sbt file. Note that these versions seem to be correct with Play 2.8 on January 2, 2022:

name         := "play-gcp-101"
organization := "com.example"
version      := "0.1.0"
scalaVersion := "2.13.7"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

libraryDependencies ++= Seq(
    guice,
    "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test,
    jdbc,
    "mysql" % "mysql-connector-java" % "8.0.27",
    "com.typesafe.play" %% "play-slick" % "5.0.0",
    "com.typesafe.play" %% "play-slick-evolutions" % "5.0.0" % Test
)

project/plugins.sbt

The project/plugins.sbt file:

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.11")
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.13.1")

conf/application.conf

It took a ridiculously long time to find the correct Play 2.8 conf/application.conf syntax/incantation:

# play.evolutions.db.default.enabled = true
slick.dbs.default.profile = "slick.jdbc.MySQLProfile$"
slick.dbs.default.db.dataSourceClass = "slick.jdbc.DatabaseUrlDataSource"
slick.dbs.default.driver= "slick.driver.MySQLDriver$"
slick.dbs.default.db.properties.url="jdbc:mysql://127.0.0.1:8889/play101?serverTimezone=UTC"
slick.dbs.default.db.user="root"
slick.dbs.default.db.password="root"
slick.dbs.default.db.connectionTimeout=5s

Note that I use a short timeout value so things fail fast while developing. Play was hanging up for 30 seconds failing to connect to the database for the first 30-60 minutes of dev time, so I found that setting. One thing that might have helped is if I had added this setting to conf/logback.xml earlier:

<!-- this can help to understand with database, connections, mysql, and hikari errors -->
<logger name="com.red.hikari" level="DEBUG" />

conf/routes

Add this to the bottom of the Play conf/routes file:

# MY ROUTES
GET     /stocks    controllers.StocksController.list

models/Stock.scala

My models/Stock.scala file:

package models

case class Stock (
    val id: Long,
    val symbol: String,
    val company: String
)

dao/StockDao.scala

My dao/StockDao.scala file:

package dao

//TODO: clean up these imports
import scala.concurrent.{ ExecutionContext, Future }
import javax.inject.Inject

import models.Stock
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.slick.HasDatabaseConfigProvider
import slick.jdbc.JdbcProfile

class StockDAO @Inject()(
    protected val dbConfigProvider: DatabaseConfigProvider
)(
    implicit executionContext: ExecutionContext
) extends HasDatabaseConfigProvider[JdbcProfile] {

    import profile.api._

    private val Stocks = TableQuery[StocksTable]

    def all(): Future[Seq[Stock]] = db.run(Stocks.result)

    // SLICK database stuff
    // this first "stocks" string refers to my database table name
    private class StocksTable(tag: Tag) extends Table[Stock](tag, "stocks") {
        def id      = column[Long](  "ID", O.PrimaryKey)
        def symbol  = column[String]("SYMBOL")
        def company = column[String]("COMPANY")
        def *       = (id, symbol, company) <> (Stock.tupled, Stock.unapply)
    }
}

controllers/StocksController.scala

My controllers/StocksController.scala class:

package controllers

import javax.inject.Inject

// TODO: clean these up.
// SEE:  https://github.com/playframework/play-slick/blob/master/samples/basic/app/controllers/Application.scala
import play.api._
import play.api.mvc._
import play.api.data.Form
import play.api.data.Forms.mapping
import play.api.data.Forms.text
import play.api.mvc.{ AbstractController, ControllerComponents }

import scala.concurrent.ExecutionContext

import models.Stock
import dao.StockDAO
import views._

class StocksController @Inject() (
    stockDao: StockDAO,
    controllerComponents: ControllerComponents
)(
    implicit executionContext: ExecutionContext
) extends AbstractController(controllerComponents) {

    // def list = Action {
    //     Ok(html.stock.list(StockDao.selectAll()))
    // }

    // TODO find a better way to do this
    def list = Action.async {
        stockDao.all().map { case (stocks) => Ok(views.html.list(stocks)) }
    }

}

views/list.scala.html

My views/list.scala.html file:

@(stocks: Seq[Stock])

@main("Stocks") {
    <h1>You have @stocks.size Stock(s)</h1>

    <div>
        <ul>
            @stocks.map { stock =>
                <li>
                    @stock.symbol
                </li>
            }
        </ul>
    </div>

}

Running and packaging

To run a Play 2.8 app:

$ sbt
sbt> run

# OR:

sbt> ~run

To package a Play 2.8 app:

sbt> show dist
[info] Your package is ready in target/universal/play-docker-gcp-101-0.1.0.zip
[info] target/universal/play-gcp-101-0.1.0.zip

To run the application, unzip the file on the target server, and then run the script in the bin directory.

References:

https://pedrorijo.com/blog/play-slick-updated/

https://medium.com/geekculture/rest-api-with-scala-play-framework-and-reactive-mongo-5016e57846a9

https://alvinalexander.com/scala/play-framework-2.8-scala-slick-mysql-sbt/

https://simpleepic.medium.com/play-framework-with-mysql-database-part-3-6136192812a6

https://sysgears.com/articles/how-to-create-restful-api-with-scala-play-silhouette-and-slick/

https://blog.knoldus.com/hands-on-crud-operation-with-scala-play/amp/

https://www.lxpert.com/showarticle/36/Build-your-first-Play-Framework-web-application

https://blog.genuine.com/2016/02/database-crud-in-scala-play/

https://dev.to/pazvanti/building-a-rest-api-in-play-framework-3agh

https://copyprogramming.com/howto/crud-operation-play-framework-example-using-angular-js