How to create rest API in Scala

How to build REST API With Scala Play Framework

This tutorial teaches the basics of building a web API with the Scala Play Framework.

In this tutorial, you learn how to:

  • Create a web API project.
  • Add a model class and a database context.
  • Create a controller with CRUD methods.
  • Configure routing, URL paths, and return values.
  • Call the web API with Postman.

In the end, you have a web API that can manage “to-do” items stored in a database.

Overview

This tutorial creates the following API:

APIDescriptionRequest bodyResponse body
GET /api/todosGet all to-do itemsNoneAn array of to-do items
GET /api/todos/{id}Get an item by IDNoneTo-do item
POST /api/todos/addAdd a new itemTo-do itemTo-do item
PUT /api/todos/{id}Update an existing itemTo-do itemNone
DELETE /api/todos/{id}Delete an itemNoneNone

The following diagram shows the architecture of the app.

Diagram of Scala rest API architecture.

Prerequisites

Create a web project

Open a terminal such as PowerShell.

Navigate to directories (cd) to the folder that will contain the project folder.

Run the following command and respond to the prompts to create a new project template

sbt new playframework/play-scala-seed.g8
name [play-scala-seed]: scala-rest-api-example
organization [com.example]: com.exampleCode language: JavaScript (javascript)

A new folder is created named scala-rest-api-example that contains the minimal project template contents.

Navigate to project folder by the following command-

cd scala-rest-api-example

Open the project folder in the visual studio code editor by the following command –

code .

Test the APP

The project template creates a basic build for a Scala Play application. It is just a minimal project to start your own application from. From the terminal run following command-

sbt run

This will download dependencies and start the system. In a browser, enter http://localhost:9000 to view the welcome page.

Screenshot of Scala play start output

Add a Model

The application manages data by using a set of model classes. In this app, we will need only a single model class. Create a Scala class named Todo.scala

package models
 
case class Todo(id: Long, name: String, isComplete: Boolean)

Add the controllers

Now it is time to add the API endpoints to which the application will respond. Create a folder named APIs under the controllers. Create a controller class named TodoController.scala

package controllers.api
 
import javax.inject._
import play.api.mvc._
import play.api.libs.json._
import models.{Todo}
 
class TodoController @Inject()(
    cc: ControllerComponents
) extends AbstractController(cc) {
 
    implicit val todoFormat = Json.format[Todo]
 
    def getAll = Action {
        val todo = new Todo(1, "item 1", false)
        Ok(Json.toJson(todo))
    }
}

Besides the controller, we will need to configure the routes file. Open conf > routes and add the following routes

#  api controllers mapping
GET     /api/todos                  controllers.api.TodoController.getAllCode language: PHP (php)

Test the API

Browse http://localhost:9000/api/todos. Run sbt run command if you have stopped the app in terminal. This should return a JSON response of one todo item

Screenshot of Scala API json Response

Congrats! We successfully created our API. Let’s modify our app that will read and manipulate data from a real database.

Creating persistent storage using MySql Database

We will use the My-SQL database to store our data. We will also use play slick that is a standard database access framework in Scala.

1. Adding Dependencies

Add the following dependencies in build.sbt file.

libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.41"
libraryDependencies += "com.typesafe.play" %% "play-slick" % "4.0.2"
libraryDependencies += "com.typesafe.play" %% "play-slick-evolutions" % "4.0.2"Code language: JavaScript (javascript)

We will use MYSQL Connector, Play-Slick to connect with the database and play slick evolution for database migrations.

2. Configurations for database connectivity

# Default database configuration using MySQL database engine
# Connect to scalatestdb as testuser
slick.dbs.default.profile = "slick.jdbc.MySQLProfile$"
slick.dbs.default.db.driver = "com.mysql.jdbc.Driver"
slick.dbs.default.db.url = "jdbc:mysql://localhost/scalatestdb"
slick.dbs.default.db.user = "testuser"
slick.dbs.default.db.password="testpass"Code language: PHP (php)

3. Integrate models with slick

We configured and mapped our database. We need to convert our models into slick objects and let the slick map our model classes and tables.

We will map the Todo class. Our case class will have no change, but we need to create another class that tells slick to map. Modify the Todo.scala as followings- 

package models
import com.google.inject.Inject
import play.api.data.Form
import play.api.data.Forms.mapping
import play.api.data.Forms._
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.jdbc.JdbcProfile
import scala.concurrent.{ExecutionContext, Future}
import slick.jdbc.MySQLProfile.api._
 
case class Todo(id: Long, name: String, isComplete: Boolean)
 
case class TodoFormData(name: String, isComplete: Boolean)
 
object TodoForm {
  val form = Form(
    mapping(
      "name" -> nonEmptyText,
      "isComplete" -> boolean
    )(TodoFormData.apply)(TodoFormData.unapply)
  )
}
 
class TodoTableDef(tag: Tag) extends Table[Todo](tag, "todo") {
 
  def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def isComplete = column[Boolean]("isComplete")
 
  override def * = (id, name, isComplete) <> (Todo.tupled, Todo.unapply)
}
 
 
class TodoList @Inject()(
    protected val dbConfigProvider: DatabaseConfigProvider
)(implicit executionContext: ExecutionContext)
    extends HasDatabaseConfigProvider[JdbcProfile] {
 
  var todoList = TableQuery[TodoTableDef]
 
  def add(todoItem: Todo): Future[String] = {
    dbConfig.db
      .run(todoList += todoItem)
      .map(res => "TodoItem successfully added")
      .recover {
        case ex: Exception => {
            printf(ex.getMessage())
            ex.getMessage
        }
      }
  }
 
  def delete(id: Long): Future[Int] = {
    dbConfig.db.run(todoList.filter(_.id === id).delete)
  }
 
  def update(todoItem: Todo): Future[Int] = {
    dbConfig.db
      .run(todoList.filter(_.id === todoItem.id)
            .map(x => (x.name, x.isComplete))
            .update(todoItem.name, todoItem.isComplete)
      )
  }
 
  def get(id: Long): Future[Option[Todo]] = {
    dbConfig.db.run(todoList.filter(_.id === id).result.headOption)
  }
 
  def listAll: Future[Seq[Todo]] = {
    dbConfig.db.run(todoList.result)
  }
}
Code language: Scala (scala)

4. Add database evolution

Database evolution facilitates migrations when you add new tables and modify any tables or delete something. Our database will be empty for now. Evolution will take care of adding the Todo table.

The evolution scripts should be in directory conf/evolutions/%databaseName% with the name of the scripts starting at 1.sql, incrementing at each evolution. %databaseName% is the default when you use the default database. Play keeps track of which evolutions have already applied in a table called play_evolutions.

Our evolution script in confi/evolutions/default/1.sql

# Todo schema
 
# --- !Ups
CREATE TABLE IF NOT EXISTS `scalatestdb1`.`todo` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NULL DEFAULT NULL,
  `isComplete` TINYINT(4) NULL DEFAULT NULL,
  PRIMARY KEY (`id`))
AUTO_INCREMENT = 2
DEFAULT CHARACTER SET = utf8
 
# --- !Downs
drop table 'todo'Code language: PHP (php)

5. Add a service class

We will add an extra service layer that will mediate communication between controller and database using models. The service layer facilitates to add business logic and isolation.

Our service class will only contain simple methods to read and write data. Create a folder named services under the app. Inside service folder create service class named TodoService.scala

package services
 
import com.google.inject.Inject
import models.{Todo, TodoList}
 
import scala.concurrent.Future
 
class TodoService @Inject() (items: TodoList) {
 
  def addItem(item: Todo): Future[String] = {
    items.add(item)
  }
 
  def deleteItem(id: Long): Future[Int] = {
    items.delete(id)
  }
 
  def updateItem(item: Todo): Future[Int] = {
    items.update(item)
  }
 
  def getItem(id: Long): Future[Option[Todo]] = {
    items.get(id)
  }
 
  def listAllItems: Future[Seq[Todo]] = {
    items.listAll
  }
}

Update the Controller class to integrate all the methods

In our Todo.scala and TodoService.Scala,we created the following methods

  • addItem
  • deleteItem
  • updateItem
  • getItem
  • listAllItems

Now modify our API Controller TodoController.scala and create an action method for each of these methods above. Also, add a parameter in the constructor to user TodoService

package controllers.api
 
import javax.inject._
import play.api.mvc._
import play.api.libs.json._
import models.{Todo, TodoForm}
import play.api.data.FormError
 
import services.TodoService
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
 
class TodoController @Inject()(
    cc: ControllerComponents,
    todoService: TodoService
) extends AbstractController(cc) {
 
    implicit val todoFormat = Json.format[Todo]
 
    def getAll() = Action.async { implicit request: Request[AnyContent] =>
        todoService.listAllItems map { items =>
          Ok(Json.toJson(items))
        }
      }
     
      def getById(id: Long) = Action.async { implicit request: Request[AnyContent] =>
        todoService.getItem(id) map { item =>
          Ok(Json.toJson(item))
        }
      }
     
      def add() = Action.async { implicit request: Request[AnyContent] =>
        TodoForm.form.bindFromRequest.fold(
          // if any error in submitted data
          errorForm => {
            errorForm.errors.foreach(println)
            Future.successful(BadRequest("Error!"))
          },
          data => {
            val newTodoItem = Todo(0, data.name, data.isComplete)
            todoService.addItem(newTodoItem).map( _ => Redirect(routes.TodoController.getAll))
          })
      }
     
      def update(id: Long) = Action.async { implicit request: Request[AnyContent] =>
        TodoForm.form.bindFromRequest.fold(
          // if any error in submitted data
          errorForm => {
            errorForm.errors.foreach(println)
            Future.successful(BadRequest("Error!"))
          },
          data => {
            val todoItem = Todo(id, data.name, data.isComplete)
            todoService.updateItem(todoItem).map( _ => Redirect(routes.TodoController.getAll))
          })
      }
     
      def delete(id: Long) = Action.async { implicit request: Request[AnyContent] =>
        todoService.deleteItem(id) map { res =>
          Redirect(routes.TodosController.getAll)
        }
      }
}Code language: Scala (scala)

Add routes for new methods

We have added new methods to our API. Let’s add routes for all API method

# api controllers mapping
GET     /api/todos                  controllers.api.TodoController.getAll
GET     /api/todos/:id                  controllers.api.TodoController.getById(id: Long)
POST    /api/todos/add              controllers.api.TodoController.add
PUT     /api/todos/update/:id           controllers.api.TodoController.update(id: Long)
DELETE /api/todos/delete/:id         controllers.api.TodoController.delete(id: Long)Code language: PHP (php)

Install new dependencies

We have added some dependencies in our build.sbt. Close the app from terminal. Execute the following command again-

sbt run

This will download dependent packages and run the app.

PLAT WITH IT

Let’s play our app and check the outputs. It should be up and running now.

Examine the Add method

This tutorial uses Postman to test the web API.

  • Install Postman
  • Start the app from command shell by running sbt run command
  • Start Postman

Test Add with Postman

  • Create a new request.
  • Set the HTTP method to POST
  • Select the Body tab.
  • Select the raw radio button.
  • Set the type to JSON (application/json).
  • In the request body enter JSON for a to-do item:
JSONCopy
{
  "name":"walk dog",
  "isComplete":true
}Code language: JavaScript (javascript)

Select Send

Screenshot of Scala API select Send response.

The response should return JSON that contains a list todo item. Including new one and previously created items.

Examine the GET methods

These methods implement two GET endpoints:

  • GET /api/todos
  • GET /api/todos/{id}

Test the app by calling the two endpoints from a browser or Postman. For example:

A response like the following is produced by the call to GetTodoItems:

JSONCopy
[{
  "id": "1",
   "name":"walk dog",
  "isComplete":true
}]Code language: JavaScript (javascript)

Test the Delete method

Use Postman to delete a to-do item:

  • Set the method to DELETE.
  • Set the URI of the object to delete (for example https://localhost:9000/api/todos/delete/1).
  • Select Send.

The final version is available at Github in case you want to check something.

You can leave comments below for any help or confusion. I will try to address those as soon as possible. Also, you can contact iXora Solution Scala Team for any assistance on your project implementation at Contact-iXoraScala

2 Responses

Add a Comment

Your email address will not be published. Required fields are marked *