Creating a REST API using Adonis v5
Tags: api, tutorial, adonis, postgres
Published: 2020-09-21 17:09:46

> In this multi-part tutorial, we'll create and deploy a multi-server architecture web application. 

> Part 1: [Creating a front-end using NuxtJS and TailwindCSS](https://dev.to/armiedema/create-and-deploy-a-multi-server-app-w-adonis-nuxt-and-cleaver-part-1-set-up-front-end-5185)
> Part 2: [Setting up Adonis v5 with PostgreSQL](https://dev.to/armiedema/setting-up-adonis-v5-with-postgresql-adl)
> Part 3: Creating a REST API using Adonis v5
> Part 4: [Connecting Nuxt front-end to Adonis 5 API](https://dev.to/armiedema/connecting-nuxt-front-end-to-adonis-5-api-using-axios-1ego)
> Part 5: [Deploying a multi-server app with Cleavr](https://dev.to/armiedema/deploying-a-multi-server-app-with-cleavr-3jh3)

#### Frameworks and tools used
* Front-end: [NuxtJS](https://nuxtjs.org/) and [TailwindCSS/UI](https://tailwindcss.com/)
* Back-end: [AdonisJS](https://adonisjs.com/) 
* Database: [PostgreSQL](https://www.postgresql.org/)
* Server provision and deployment: [Cleavr](https://cleavr.io)

# Part 3 - Creating a REST API using Adonis v5 and PostgreSQL
Let's dig a little deeper into Adonis and create a Rest API. For this, we'll need to create a data model, routes, and controller. We'll also set up a database table migration and create a seeder to seed the database with. 

## Set up RESTful API routes

{% youtube oH9DLZyHiDE %}

Adonis makes the standard CRUD operations easily available for you so that you don't have to define everything yourself. 

Using `Route.resource()` will automatically set up all of the typical CRUD routes for you. Let's add in the following route: 
```typescript
Route
  .resource('movies', 'MoviesController')
  .only(['index', 'show'])
  .apiOnly()
```
What this does is set up our CRUD routes for `/movies` and assigns them to refer to the `MoviesController`, in which we'll define later on. 

However, since I am making a simple app and am only interested in fetching either all of the movies or a single movie, I'll use `.only()` to only include the `index` and `show` routes. 

We'll also add `.apiOnly()`. Otherwise, `.resource()` will create form routes; which, we don't need. 

Run the following command in your terminal to verify that only the `index` and `show` routes are created. 

```
node ace list:routes
```

## Make a data model class

{% youtube tNl6RtiueHs %}

To create a new model, use the following command: 
```
node ace make:model Movie
```
This operation creates a new file at `/app/Models/Movie.ts` and gives us a head start with creating the model. 

Recalling the data points that we need for our front-end, we'll define the model class for Movies as follows: 

```typescript
import { DateTime } from 'luxon'
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'

export default class Movie extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  public title: String

  @column()
  public posterImage: String

  @column()
  public releaseYear: Number

  @column()
  public topBilled: String

  @column()
  public genres: String

  @column()
  public movieDescription: String

  @column()
  public movieReview: String

  @column.dateTime({ autoCreate: true })
  public createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  public updatedAt: DateTime
}
```

## Make a data migration and migrate the database

{% youtube lFmTpY3PVx0 %}

Run the following command to create a new migration: 

```
node ace make:migration movies
```

This will create a file located at `/database/migrations/<timestamp>_movies.ts`.

Let's navigate to the migration file and add in the table columns. 

```typescript
import BaseSchema from '@ioc:Adonis/Lucid/Schema'

export default class Movies extends BaseSchema {
  protected tableName = 'movies'

  public async up () {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id')
      table.timestamps(true)
      table.string('title').notNullable()
      table.string('poster_image').notNullable()
      table.string('release_year').notNullable()
      table.string('top_billed').notNullable()
      table.string('genres').notNullable()
      table.string('movie_description').notNullable()
      table.string('movie_review').notNullable()
    })
  }

  public async down () {
    this.schema.dropTable(this.tableName)
  }
}
```

Time to migrate the database by running the following command: 

```
node ace migration:run
```

Let's double check our database to see if the table migrated successfully. 

I like to use [TablePlus](https://tableplus.com/) to manage my databases; so, I'll open that up to check that a migration row was added to the `adonis_schema` table and that my movies table and it's columns were properly migrated. And... They look good! 🎉


## Make a seeder and seed the database

{% youtube ybW5SKpcVIU %}

We'll now set up a seeder to seed our database with the example movies that we identified while creating the front-end - Children of Men, The Terminator, and The Sisterhood of the Traveling Pants. 

``` 
node ace make:seeder Movies
```
The above command will output a new seeder file at `databases/seeders/Movies.ts`. 

Let's import the `Movie` data model and then add JSON for the 3 sets of movie details we want to seed our database with. 

```typescript
import BaseSeeder from '@ioc:Adonis/Lucid/Seeder'
import Movie from "App/Models/Movie";

export default class MovieSeeder extends BaseSeeder {
  public async run () {
    await Movie.createMany([
      {
        title: 'Children of Men',
        posterImage: 'https://m.media-amazon.com/images/M/MV5BMTQ5NTI2NTI4NF5BMl5BanBnXkFtZTcwNjk2NDA2OQ@@._V1_.jpg',
        releaseYear: 2006,
        topBilled: 'Clive Owen, Julianne Moore, Michael Cane',
        genres: 'Adventure, Drama, Sci-Fi',
        movieDescription: 'In 2027, in a chaotic world in which women have become somehow infertile, a former activist agrees to help transport a miraculously pregnant woman to a sanctuary at sea.',
        movieReview: 'The movie is pretty good. It\'s known for long, seemingly un-cut sequences. It\'s better than Gravity. It\'s not as good as Galaxy Quest. You should watch it. I mean, what else are you doing?'
      },
      {
        title: 'The Terminator',
        posterImage: 'https://m.media-amazon.com/images/M/MV5BYTViNzMxZjEtZGEwNy00MDNiLWIzNGQtZDY2MjQ1OWViZjFmXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_SY1000_CR0,0,666,1000_AL_.jpg',
        releaseYear: 1984,
        topBilled: 'Arnold Schwarzenegger, Michael Biehn, Linda Hamilton',
        genres: 'Action, Sci-Fi',
        movieDescription: 'A human soldier is sent from 2029 to 1984 to stop an almost indestructible cyborg killing machine, sent from the same year, which has been programmed to execute a young woman whose unborn son is the key to humanity\'s future salvation.',
        movieReview: 'Like most movies that turn into a series, the 2nd one is wayyyyy better. In this movie, Schwarzenegger is the bad guy. The great thing for him, is that he\'s not required to act and just plays his normal self. Hollywood. A wonderland!'
      },
      {
        title: 'The Sisterhood of the Traveling Pants',
        posterImage: 'https://m.media-amazon.com/images/M/MV5BNmRjYWE3OTQtYzEwOC00OWM4LTk3MzktZTUyZTgzNjY4NDc0L2ltYWdlL2ltYWdlXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_UX182_CR0,0,182,268_AL_.jpg',
        releaseYear: 2005,
        topBilled: 'Amber Tamblyn, Alexis Bledel, America Ferrera, Blake Lively',
        genres: 'Comedy, Drama, Romance',
        movieDescription: 'Four best girlfriends hatch a plan to stay connected with one another as their lives start off in different directions: they pass around a pair of secondhand jeans that fits each of their bodies perfectly.',
        movieReview: 'This movie is about the greatest miracle the world has seen. The pants fit them all! Furthermore, the pants travel all over the world. They\'re like a traveling gnome. But, more fun to watch!'
      },
    ])
  }
}
```

Now, to seed the database, run: 

``` 
node ace db:seed
```

At this step, I was asked to install `luxon` prior to being able to successfully run the seed command. You can install this package running the following: 

```
npm install --save luxon
```

Once you have successfully ran the seed command, go back to your database management client and verify that the data was correctly seeded. 


## Make a controller class

{% youtube LvtQLb2xTr4 %}

Time to create the controller class! 

```
node ace make:controller MoviesController
```

This will create a controller file at `app/Controllers/Http/MoviesController.ts`.

Head over to the new file and we'll start by adding in the Movie model. 

```typescript
import Movie from "App/Models/Movie"
```

Next, we'll add in our index function. 

```typescript
export default class MoviesController {

  public async index () {
    const movies = await Movie.all()
    return movies
  }
}

```
The `.all()` method is a shorthand to query and return everything from the movies table.  

We'll use this route for our homepage that displays the list of movies. 

Now, for our details page, we only want to return data for a single movie. 

Let's add in the following to our `MovieController` class:

 ```typescript
public async show ({ params }) {
    const movie = await Movie.find(params.id)
    return movie
  }
```
We're passing in `params.id` in the `.find` function to pull the movie id from the request url and then looking up the movie in our database using the returned id. 

We now have all routes and controller defined. Time to test the API! 😎

## Test the API

{% youtube Do2VLkhdGQk %}

To test the API, I'll use [Postman](https://www.postman.com/); though, you can use your favorite web browser if you do not want to use an API client. 

In Postman, I'll make a `Get` request and add in my local address and append the url with `/movies`. 

This request will trigger the `index` router and then return the full list of movies as we defined in `MoviesController`. 

![CleanShot 2020-09-20 at 11.11.10@2x](https://dev-to-uploads.s3.amazonaws.com/i/z38rekxc9kaljbt89tij.png)

It works! 

Now, let's check passing a movie id to verify it only returns  data for that movie. 

![CleanShot 2020-09-20 at 11.11.32@2x](https://dev-to-uploads.s3.amazonaws.com/i/at26ejb9tfw41f9bnui6.png)

Success! 🔥

We now have our API ready and will update the front-end to dynamically call and place the data from the API in [part 4](https://dev.to/armiedema/connecting-nuxt-front-end-to-adonis-5-api-using-axios-1ego).  

---
Following along? View Part 2 progress on GitHub at https://github.com/armgitaar/moviesapi. 

[Watch the full tutorial series uninterrupted on Youtube.](https://www.youtube.com/playlist?list=PLApnZzwJN4G5gHKMr4owbFKUdAtdwPB-X)