I am following a tutorial on building an app with Express, Nodejs, Sequelize,Postgres.
After creating my controller and routes, the GET route works perfectly but POST route (mean to call a callback function for creating an object) fails with:
TypeError: cannot read property 'create' of undefined.
This is the controller:
let Todo = require('../models').todo;
module.exports = {
create(req, res){
return Todo
.create({title:req.body.title,})
.then(todo => res.status(201).send(todo))
.catch(error => res.status(400).send(error));
},
list(req, res){
return Todo
.all()
.then(todos => res.status(200).send(todos))
.catch(error => res.status(400).send(error));
},
};
And this is the route definition
const todosController = require('../controllers').todos;
module.exports = app => {
app.get('/api', (req, res) =>res.status(200).send({message:"welcome to the todos API!"}));
app.post('/api/todos', todosController.create);
app.get('/api/todos', todosController.list);
};
...and here is the todo model. Thanks #Yuri Tarabanko
'use strict';
module.exports = (sequelize, DataTypes) =>{
const Todo = sequelize.define('Todo', {
title:{
type:DataTypes.STRING,
allowNull: false
},
},
{
classMethods: {
associate: (models) => {
Todo.hasMany(models.TodoItem,
{
foreignKey:'todoId',
as: 'todoItems',
});
},
},
}
);
return Todo;
};
My controllers/index.js file is shown below.
const todos = require('./todos');
module.exports = {
todos,
};
It is because you are importing wrong...
Change:
const todosController = require('../controllers').todos;
to
const todosController = require('../controllers/todos')
Should be not
const todosController = require('../controllers').todos;
but
const todosController = require('../controllers');
because your create and list functions are not in todo but in root of module.exports.
create and list functions are actually in the "todo.js" file in the
"controllers" directory.
Then you should require them as
const todosController = require('../controllers/todo');
unless you have controllers/index.js which has
modules.exports={todo:require(todo)};
Also note that your file seems to be called todo, not todos.
Related
Here is my code snippet
Folder Scafolding
backendstuff (the express app)
- models/currentweather.js
- models/sequelize.js
- routes/weather.js
app.js
models/currentweather.js
const { Sequelize, DataTypes, Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
const CurrentWeather = sequelize.define("CurrentWeather", {
// Model attributes are defined here
name: DataTypes.STRING,
region: DataTypes.STRING,
country: DataTypes.STRING,
lat: DataTypes.FLOAT,
lon: DataTypes.FLOAT,
tz_id : DataTypes.STRING
});
return CurrentWeather;
}
models/sequelize.js
const Sequelize = require("sequelize");
const CWeatherModel = require('./currentweather');
const FWeatherModel = require('./forecastweather');
const sequelize = new Sequelize(
'climate_db',
'uname',
'password',
{
host: 'localhost',
dialect: 'mysql'
}
);
const CWeather = CWeatherModel(sequelize, Sequelize)
const FWeather = FWeatherModel(sequelize, Sequelize)
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
// sequelize.sync()
// .then(() => {
// console.log(`Database & tables created!`)
// })
module.exports = {
CWeather,
FWeather
}
routes/weather.js
var express = require('express');
var router = express.Router();
const axios = require('axios');
const { CurrentWeather } = require('../models/currentweather');
const { ForecastWeather } = require('../models/forecastweather');
// Added as per recommendation
const { Sequelize, DataTypes, Model } = require('sequelize');
require('dotenv').config();
var request=require('request');
var apiconf = require('../apiconfig.json');
var apikey = process.env.APIKEY,
apiBaseurl = 'http://api.weatherapi.com/v1/';
router.get('/current', function(req, res, next){
// API Returns the json - working!!
apiurl = 'https://someapiurl.com?weather';
axios.get(apiurl)
.then(response => {
console.log(response.data);
// Storing the Response in location piece from the huge response
// Throws this error now:
// TypeError: CurrentWeather is not a function
const cweather = CurrentWeather(Sequelize, DataTypes).create({
name: location.name,
region: location.region,
country: location.country,
lat: location.lat,
lon: location.lon,
tz_id: location.tz_id
});
cweather.save();
return res.json(response.data);
})
})
Looks like everything is OKAY, but I get this error, what is that I am missing ?
const cweather = CurrentWeather.create({
^
TypeError: Cannot read properties of undefined (reading 'create')
currentweather.js exports a function which returns an instance of Sequelize's CurrentWeather, while you're trying to act as if the function itself is a creation of Sequelize.
You forgot to pass sequelize and DataTypes to the function. Your weather.js code should look something like this instead:
const cweather = CurrentWeather(sequelizeInstance, yourDataTypes).create({
name: location.name,
region: location.region,
country: location.country,
lat: location.lat,
lon: location.lon,
tz_id: location.tz_id
});
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.
I already solved the problem but now IT is back. I want to save new artist to the website or edit them but now its like the application doesnt connect to the DB but it is! I checked.
const
express = require('express'),
router = express.Router(),
Post = require('../models/post');
// Define which ImageTypes are avaiable to upload
const imageMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/jpg'];
// All admin Routes
router.get('/', async (req, res) => {
renderNewPage(res, new Post())
})
// New Route
router.get('/create', (req, res) => {
res.render('admin/createPost/index', {
layout: 'layouts/admin',
// Defined in models/post, gets variables
post: new Post()
});
})
// New Post create
router.post('/', async (req, res) => {
const post = new Post({
// get information from sended new Post (above) and defines variables to use in ejs files
surname: req.body.surname,
name: req.body.name,
bio: req.body.bio,
birthday: req.body.birthday,
technic: req.body.technic,
techPricerangeFrom: req.body.techPricerangeFrom,
techPricerangeTo: req.body.techPricerangeTo
})
// set FilePond saving files to DB
saveProfilpic(post, req.body.profilpic)
try { // await for new post to safe
const newPost = await post.save();
res.redirect('admin');
} catch {
renderNewPage(res, post, true)
}
})
//? Singe Artist Show, Edit, Upate, Delete
// Create single artists page
router.get('/:id', (req, res) => {
res.send('Show Artist' + req.params.id)
})
// Edit artist
router.get('/:id/edit', async (req, res) => {
try {
const findPost = await Post.findById(req.params.id)
res.render('admin/edit', {
layout: 'layouts/admin',
post: findPost
})
} catch {
res.redirect('/admin')
}
})
// Update artist text
router.put('/:id', async (req, res) => {
let post;
try {
post = await Post.findById(req.params.id)
post.surname = req.body.surname,
post.name = req.body.name,
post.bio = req.body.bio,
post.birthday = req.body.birthday,
post.technic = req.body.technic,
post.techPricerangeFrom = req.body.techPricerangeFrom,
post.techPricerangeTo = req.body.techPricerangeTo,
post.profilpic = req.body.profilpic
await post.save();
res.redirect(`/admin`);
} catch {
if (post == null) {
res.redirect('/admin')
}
else {
res.render('admin/edit', {
layout: 'layouts/admin',
post: post,
})
}
}
})
// Define functions
async function renderNewPage(res, post, hasError = false) {
// Implement all Posts (artists) in the DB
try {
const posts = await Post.find({}).collation({ locale: 'en', strength: 2 }).sort({ name: 1 }) // wait and find all post and sort my name
const params = {
layout: 'layouts/admin',
posts: posts, // take all posts from await Post.find({}) and overrides the updates the posts
}
if (hasError) params.errorMessage = 'Ein Fehler ist aufgetreten'
res.render('admin/index', params);
} catch (err) {
res.redirect('/');
}
}
// func for dave files via filepond
function saveProfilpic(post, profilpictureEncoded) {
if (profilpictureEncoded == null) return
const profpic = JSON.parse(profilpictureEncoded);
if (profpic != null && imageMimeTypes.includes(profpic.type)) { // If the file is a json obj & from the type image (jpg, png)
post.profilpic = new Buffer.from(profpic.data, 'base64') // Buffer.from(where, how)
post.profilpicType = profpic.type
}
}
module.exports = router;
all types in the model/post file are Strings.If I want to change something it redirects me to /admin, what means its a error. I got all latest packages (express, mongoose, ...)
I found your answer as I was working on the same tutorial, so I'm still quite new to this, but in my case the problem was related to having a missing '=' in the index page view.
Do you have the '=' character within the <% %> part so that the id is actually included in the link? If not, the link won't include the actual id, since the '=' is needed to not just access a variable but also display it.
<% artists.forEach(Artist=> { %>
<div><%= Artist.last_name %></div>
View
<% }) %>
Hope this helps, and feel free to correct me, as I'm also just starting to learn the basics.
My console shows the following error:
POST /log-in 200 637.310 ms - 42
Unhandled rejection TypeError: models.UserAccount.setJwTokenCookie is not a function
at models.UserAccount.findOne.then.userRecord (/home/owner/PhpstormProjects/reportingAreaApi/routes/index.js:99:33)
at tryCatcher (/home/owner/PhpstormProjects/reportingAreaApi/node_modules/bluebird/js/release/util.js:16:23)
I have been attempting to add instance methods to the UserAccount model definition. The method setJwTokenCookie appears to be at fault below:
'use strict';
require('dotenv').load();
const bcryptjs = require('bcryptjs'),
jsonWebToken = require('jsonwebtoken'),
moment = require('moment');
module.exports = (sequelize, DataTypes) => {
const UserAccount = sequelize.define('UserAccount', {
username: DataTypes.STRING,
password: DataTypes.STRING
});
// cookie-setter
UserAccount.prototype.setJwTokenCookie = (responseLocal, userId) => {
// generate a new jwt encoded with userId:
const signedToken = jsonWebToken.sign({
data: {
userId : userId
}
}, <secret>);
const dateIn10Years = new moment()
.add(10, "years").toDate();
responseLocal.cookie('jwTokenCookie', signedToken, {
httpOnly: true,
expires : dateIn10Years
})
};
return UserAccount;
};
I am trying to follow the instance method format shown here: http://docs.sequelizejs.com/manual/tutorial/models-definition.html#expansion-of-models
Does anyone know the source of this error? I am working inside of an Express.js project and the error fires when a post request is made to the relevant route handler.
The method is called here:
router.post('/log-in', passport.authenticate('local'), (req, res) => {
// get the user's Id. Then set the cookie with it
models.UserAccount.findOne({ where : { username : req.body.username }})
.then(userRecord => {
const { userId } = userRecord;
return models.UserAccount.setJwTokenCookie(res, userId);
});
You have defined it as a instance method:
UserAccount.prototype.setJwTokenCookie
But are calling it as a class method:
models.UserAccount.setJwTokenCookie
It should just be:
.then(userRecord => {
const { userId } = userRecord;
return userRecord.setJwTokenCookie(res, userId);
});
I'm trying to create a recipe search app using Express as my backend, and am getting the error message in the title from my Router file when I start my server up. I've made projects before where my Router follows the same as it does here, but for some reason I keep getting the error and I can't figure out why. Below I've pasted the code to my controller, helper, and route files:
Controller:
// import model and users controller
const Recipe = require('../models/recipe');
const usersController = require('../controllers/users-controller');
// initiate controller object
const recipesController = {}
// send API data
recipesController.sendApiRecipe = (req, res) => {
res.json({
message: `recipe returned`,
recipe: res.locals.recipe,
})
}
// show all favorited recipes
recipesController.index = (req, res, next) => {
Recipe.findByUser(req.user.id)
.then(recipe => {
res.json({
message: 'rendering favorites',
data: { recipe },
})
}).catch(next)
}
// create favorite recipe
recipesController.create = (req, res) => {
console.log(req.body, 'from create/recipesController')
Recipe.create({
title: req.body.title,
diet: req.body.diet,
calories: req.body.calories,
servings: req.body.servings,
health: req.body.health,
ingredient: req.body.ingredient,
img: req.body.img,
link: req.body.link,
user_id: req.user.id,
}).then(recipe => {
res.json({
message: 'successfully added',
data: { recipe }
})
}).catch(err => {
console.log(err)
res.status(500).json({error: err})
})
}
// delete favorite recipe
recipesController.delete = (req, res, next) => {
Recipe.destroy(req.params.id)
.then(() => {
res.json({
message: 'successfully deleted recipe',
})
}).catch(next)
}
export default recipesController;
Helper:
// import dependencies
require('isomorphic-fetch')
require('dotenv').config()
function getRecipes(req, res, next) {
// fetch URL
fetch(`https://api.edamam.com/search?q=${req.params.search}&app_id=${process.env.APP_ID}&app_key=${process.env.APP_KEY}&from=0&to=30`)
.then(res => res.json())
// use res.locals to attach data to repsonse object
.then(fetchRes => {
// set fetched results to res.locals
res.locals.recipe = fetchRes
next()
})
}
// export function
module.exports = {
getRecipes: getRecipes,
}
Routes:
// import dependencies
const express = require('express')
const recipeHelpers = require('../services/recipes/recipe-helpers')
const recipesController = require('../controllers/recipes-controller')
const recipesRouter = express.Router()
recipesRouter.get('/:search', recipeHelpers.getRecipes, recipesController.sendApiRecipe)
recipesRouter.post('/create', recipesController.create)
module.exports = recipesRouter;
Please let me know if there's any additional information I should provide, and I'll be certain to follow up with any findings I come across as I troubleshoot further. Thanks in advance for any help!
I just realized I didn't export my controller properly, and fixing that resolved the issue. Thanks!