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
Part 2: Setting up Adonis v5 with PostgreSQL
Part 3: Creating a REST API using Adonis v5
Part 4: Connecting Nuxt front-end to Adonis 5 API
Part 5: Deploying a multi-server app with Cleavr
Frameworks and tools used
- Front-end: NuxtJS and TailwindCSS/UI
- Back-end: AdonisJS
- Database: PostgreSQL
- Server provision and deployment: Cleavr
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
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:
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
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:
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
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.
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 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
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.
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
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.
import Movie from "App/Models/Movie"
Next, we'll add in our index function.
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:
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
To test the API, I'll use Postman; 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
.
It works!
Now, let's check passing a movie id to verify it only returns data for that movie.
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.
Following along? View Part 2 progress on GitHub at https://github.com/armgitaar/moviesapi.
Top comments (0)