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.
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.