1

In learning how to use Objection.js, I am interested in learning how to implement a join table and populate it with the associated foreign keys. I made some progress but I'm not sure I am setting things up correctly. I created a smaller side project from my main project that is simplified so I can test without all the extra noise from the code I do not need to troubleshoot. So far, I can set this ORM up fine with no errors. Now I am interested in utilizing join tables and turn to the StackOverflow Community for any feedback I may be given. Going through the documentation, I can see that I would need to make use of the 'extra' property inside my relationMappings() method.

I made sure to create the correct mapping for each model, Actors, Movies and ActorsMovies. I also made sure to create a model for the join table. When I first started testing, I added the 'extra' property to the migration of the 'actors_movies' table as a string, then changed the data type to integer because ultimately, that is how I intend on using it. In order for this to be implemented correctly, do I only need one 'extra' property? Because I added a second 'extra' property named 'author'. So, the two are now 'character' used in the Actor model and 'author' in the Movie model.

enter image description here

Additional pages from Objection that I referenced are the following: Join Table Recipe and Ternary relationships Recipe

My small test comes from the examples that were provided in the Objection documentation, so that will be the point of reference I will put here. Three tables: Actors, Movies and ActorsMovies.

const { Model } = require('objection');
const knex = require('../db/dbConfig');

Model.knex(knex);


class Actor extends Model {
    static get tableName() {
        return 'actors'
    }

    static get relationMappings() {
    const Movie = require('./Movie')
      return {
        movies: {
            relation: Model.ManyToManyRelation, 
            modelClass: Movie,
            join: {
                from: 'actors.id',
                through: {
                    from: 'actors_movies.actor_id',
                    to: 'actors_movies.movie_id',
                    extra: {
                      character: 'character'
                    }
                },
                to: 'movies.id'
            }
        }
      };
    }
  }

module.exports = Actor;

//Movies.js
const { Model } = require('objection');
const knex = require('../db/dbConfig');

Model.knex(knex);


class Movie extends Model {
    static get tableName() {
        return 'movies'
    }

    static get relationMappings() {
    const Actor = require('./Actor')
      return {
        actors: {
            relation: Model.ManyToManyRelation, 
            modelClass: Actor,
            join: {
                from: 'movies.id',
                through: {
                    from: 'actors_movies.movie_id',
                    to: 'actors_movies.actor_id',
                    extra: {
                      author: 'author'
                    }
                },
                to: 'actors.id'
            }
        }
      };
    }
  }

module.exports = Movie;

//ActorsMovies.js
const { Model } = require('objection');
const knex = require('../db/dbConfig');

Model.knex(knex);


class ActorsMovies extends Model {
    static get tableName() {
      return 'actors_movies';
    }
  
    static get idColumn() {
      return ['actor_id', 'movie_id'];
    }
  
    static get relationMappings() {
    const Actor = require('./Actor');
    const Movie = require('./Movie');
      return {
        actor: {
          relation: Model.BelongsToOneRelation,
          modelClass: Actor,
          join: {
            from: 'actors_movies.actor_id',
            to: 'actors.id'
          }
        },
  
        movie: {
          relation: Model.BelongsToOneRelation,
          modelClass: Movie,
          join: {
            from: 'actors_movies.movie_id',
            to: 'movies.id'
          }
        }
      };
    }
  }
  

module.exports = ActorsMovies;

For this test project, I am interested in making sure the ActorsMovies table gets correctly populated with the actor_id and the movie_id when a movie is created with a POST request.

// api/actors.js
const express = require('express');
const router = express.Router();
const Actors = require('../models/Actor');
const Movies = require('../models/Movie');

/************************/
/********* READ *********/
/************************/

router.get('/', async (req, res, next) => {
    try {
        const user = await Actors.query();
        res.status(200).json(user)
    } catch(error) {
        console.log(error.message)
    }
});

router.get('/:id', async (req, res, next) => {
    try {
        const actorId = req.params.id;
        const actor = await Actors.query().findById(actorId);

        const movie = await Actors.relatedQuery('movies')
            .for(actor.id)
            .insert({ name: actor.name, character: actor.id }).debug()
            
        res.status(200).json(movie)
    } catch (error) {
        console.log(error.message)
    }
});

module.exports = router;

// api/movies.js 
const express = require('express');
const router = express.Router();
const Movies = require('../models/Movie');

/************************/
/********* READ *********/
/************************/

router.get('/', async (req, res, next) => {
    try {
        const movie = await Movies.query();
        res.status(200).json(movie)
    } catch(error) {
        console.log(error.message)
    }
});

router.get('/:id', async (req, res, next) => {
    try {
        const movieId = req.params.id;
        const movie = await Movies.query().findById(movieId);

        const actor = await Movies.relatedQuery('actors')
            .for(movie.id)
            .insert({ name: 'The Room', author: movie.id }).debug();

        res.status(200).json(actor)
    } catch (error) {
        console.log(error.message)
    }
});

/************************/
/******** CREATE ********/
/************************/

router.post('/', async (req, res, next) => {
    try {
        const createMovie = req.body;
        const newMovie = await Movies.query().insert(createMovie);

        const actor = await Movies.relatedQuery('actors')
            .for(newMovie.id)
            .insert({ name: newMovie.name, author: newMovie.id }) 

        res.status(201).json(actor)
    } catch (error) {
        console.log(error.message)
    }
});

module.exports = router;

//migration file 

exports.up = knex => {
    return knex.schema
      .createTable('actors', table => {
        table.increments('id').primary();
        table.string('name');
        table.timestamps(false, true);
      })
      .createTable('movies', table => {
        table.increments('id').primary();
        table.string('name');
        table.timestamps(false, true);
      })
      .createTable('actors_movies', table => {
        table.integer('actor_id').references('actors.id');
        table.integer('movie_id').references('movies.id');
        // The actor's character's name in the movie.
        table.integer('character');
        table.integer('author');
        table.timestamps(false, true);
      });
  };

  exports.down = function(knex) {
    return knex.schema
      .dropTableIfExists('actors_movies')
      .dropTableIfExists('movies')
      .dropTableIfExists('actors')
  };

// dbConfig.js
require('dotenv').config();
const environment = process.env.NODE_ENV || 'development'
const config = require('../knexfile.js')[environment]

module.exports = require('knex')(config)

The server works fine, the connection between knex.js and Objection.js is fine too. I get a clean response in Postman, but I'm hoping to get an experienced opinion on how I am implementing this. As a side note, I did scour StackOverflow and did not find my specific question, so your feedback will be greatly appreciated.

0 Answers0