Generating Movie Posters in NuxtJS

Banner for a MediaJam post

Eugene Musebe


Consuming APIs and generating custom images from the data received is a wholesome task. Let us explore how we can generate dynamic movie posters for films of the TMDB database.


The completed project is available on Codesandbox.

You can find the full codebase on my Github

App Setup

For this article, we will be using Nuxt.Js, an amazing Vue.Js framework. To get started, ensure you have Yarn or NPM v5.2+ or v6.1+ installed. Open your preferred working directory and run the following command:

1yarn create nuxt-app nuxtjs-movie-posters
2# OR
3npx create-nuxt-app nuxtjs-movie-posters
4# OR
5npm init nuxt-app nuxtjs-movie-posters

This will result in a series of various setup questions. Here are our recommended defaults:

Setup defaults

Once the setup is complete, you may enter and run your app.

1cd nuxtjs-movie-posters
3yarn dev
4# OR
5npm run dev

Your app will now be running on localhost:3000.

Cloudinary setup

We will be using cloudinary's powerful Nuxt.Js SDK to manipulate our images. Login to your Cloudinary console and copy your cloud-name. If you do not have an account, feel free to create one here. We are going to add our cloud-name to our .env file. Let's create the file.

1touch .env

The .env file will house our environmental variables. These are values we do not want to be stored in our codebase.

1<!-- .env -->

We can now install the recommended Nuxt.Js plugin.

1yarn add @nuxtjs/cloudinary
2# OR
3npm install @nuxtjs/cloudinary

Add it as a module in the modules section of the nuxt.config.js file.

1// nuxt.config.js
2export default {
3 ...
4 modules: [
5 '@nuxtjs/cloudinary'
6 ]
7 ...

Add a cloudinary section to configure the plugin. In this section, we will be linking it to our Cloudinary account by registering our cloud-name.

1export default {
2 ...
3 cloudinary: {
4 cloudName: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
5 useComponent: true
6 }

You are now ready to use Cloudinary in your project.

TMDB Setup

We will be getting the movie details from TMDB (The Movie Database). First, create an account here if you do not have one. Once registered and logged in, proceed to the API setting section to obtain your API key. You will have to answer a few questions with regards to your API consumption.

We will now add the API key to our .env file.

1<!-- .env -->

Searching for the movie

First and foremost, we want our users to either search for a film of their choice or obtain a random film. Let us add a basic HTML form for this purpose.

1<!-- pages/index.vue -->
3 ...
4 <form @submit.prevent="search">
5 <div>
6 <label for="search">Search</label>
7 <div>
8 <input
9 v-model="searchQuery"
10 type="text"
11 name="search"
12 id="search"
13 placeholder="Search for movie..."
14 />
15 </div>
16 </div>
17 <div>
18 <button
19 v-if="!loading"
20 type="submit"
21 >
22 {{searchQuery ? "Search" : "Random"}}
23 </button>
24 <p v-if="loading">
25 {{loading}}
26 </p>
27 </div>
28 </form>
29 ...

The above form accepts a search input and triggers the search method when submitted. When there is no search input, the button reads Random but submits to the same method. Let us now prepare the corresponding JavaScript.

1// pages/index.vue
3export default {
4 data(){
5 return {
6 loading:false,
7 searchQuery: null,
8 show:null,
9 };
10 },
11 mounted(){
13 },
14 methods:{
15 search(){
16 Array.prototype.random = function () {
17 return this[Math.floor((Math.random()*this.length))];
18 }
20 this.loading = "Getting search results...";
21 const requestOptions ={
22 method: 'GET',
23 redirect: 'follow'
24 };
26 const requestURL = "q?";
28 const alphabet = "abcdefghijklmnopqrstuvwxyz";
30 const queryParams = new URLSearchParams({
31 api_key: process.env.NUXT_ENV_TMDB_API_KEY,
32 query: this.searchQuery ?? [...alphabet].random(),
33 });
35 fetch(requestURL + queryParams, requestOptions)
36 .then(response => response.json())
37 .then(
38 response => = response
39 .results
40 .filter(show => show.backdrop_path)
41 .random()
42 )
43 .then(() => this.loading="Loading image..")
44 .catch(error => alert('Error:' + error));
45 },
46 }

The above search method first declares a method to get random elements from arrays. We then proceed to query the route with the search query and our API key. If there is no search string, a random letter is used as the search query. Once the request is successful, the results are filtered to remove those without a backdrop_path. This is the path that contains the image we will use for the poster. A random show from the remaining is assigned to the show variable in the state and the loading indicator is changed to "Loading image..." from "Getting search results...".

Generating the poster

To generate the poster, we will utilize the globally injected $cloudinary instance. If there is no show, return nothing. Otherwise, fetch the image from the remote source.

Once the image is fetched we overlay the title with the random text color. We place the title at the bottom of the poster which is the norm with movie posters.

1// pages/index.vue
2export default{
3 ...
4 computed:{
5 posterURL(){
6 if(!{
7 return;
8 }
9 return this.$cloudinary.image.fetchRemote(
10 `${}`,
11 {
12 width:800,
13 height:800,
14 crop:"fit",
15 transformation: [
16 {
17 color:"#" + Math.floor(Math.random()*16777215).toString(16),
18 overlay: {
19 font_family: "Ultra",
20 font_weight: "bold",
21 font_size: 120,
22 text:
23 }
24 },
25 {flags: "layer_apply", gravity: "south", y: 100}
26 ]
27 }
28 );
29 }
30 },
31 ...

We customize the title using the Ultra font, boldness and 120 as the font size. Now that our URL is ready, let us display it.

1<!-- pages/index.vue -->
3 ...
4 <div>
5 <img
6 :alt="`${show.title} poster`"
7 v-if="show"
8 :src="posterURL"
9 referrerpolicy="no-referrer"
10 @load="loading = null"
11 @error="search"
12 />
13 </div>
14 ...

The above HTML code displays the poster when show is not empty or negative. The no-referrer policy enables us to obtain the image from our app as the image is restricted to direct browser visits only.

Once the image is loaded, we remove the loading indicator. However, if the image loading fails, we load a different image by triggering the search all over again.

When it is all done, we should have an output similar to the following:

Final output


To learn more about how to interact with the TMDP API, feel free to browse their documentation.

You may review the Cloudinary package documentation to learn more about its capabilities as well as review the official Cloudinary docs.

Eugene Musebe

Software Developer

I’m a full-stack software developer, content creator, and tech community builder based in Nairobi, Kenya. I am addicted to learning new technologies and loves working with like-minded people.