Меня медленно сводит с ума эта проблема.
Используя локальный экземпляр сервера Apollo, к которому обращается Apollo Studio, я пытаюсь выполнить простую мутацию, createUser, и возникает эта проблема. Что я неправильно понял?
Я неправильно использую контекст, который я предоставил при создании сервера? Или неправильный доступ к этой модели, может быть? Точно сказать не могу!
Вот ошибка, отображаемая в Apollo Studio, за которой следуют мои файлы:
{
"errors": [
{
"message": "Cannot read properties of undefined (reading 'findOne')",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createUser"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"TypeError: Cannot read properties of undefined (reading 'findOne')",
" at Object.createUser (/Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/graphql/resolvers/user.js:22:46)",
" at field.resolve (/Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/node_modules/apollo-server-core/dist/utils/schemaInstrumentation.js:56:26)",
" at executeField (/Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/node_modules/graphql/execution/execute.js:479:20)",
" at /Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/node_modules/graphql/execution/execute.js:375:22",
" at promiseReduce (/Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/node_modules/graphql/jsutils/promiseReduce.js:23:9)",
" at executeFieldsSerially (/Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/node_modules/graphql/execution/execute.js:371:43)",
" at executeOperation (/Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/node_modules/graphql/execution/execute.js:345:14)",
" at execute (/Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/node_modules/graphql/execution/execute.js:136:20)",
" at execute (/Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/node_modules/apollo-server-core/dist/requestPipeline.js:205:48)",
" at processGraphQLRequest (/Volumes/T7 Touch/Projects/PassTheArt/pass-the-art-server/node_modules/apollo-server-core/dist/requestPipeline.js:148:34)"
]
}
}
}
],
"data": null
}
// ./server.js
require('dotenv').config();
import express from 'express';
import db from './db';
import resolvers from './graphql/resolvers';
import typeDefs from './graphql/typeDefs';
import http from 'http';
import { ApolloServer } from 'apollo-server-express';
async function startApolloServer(){
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
playground: true,
context: async() => {
return {
db
}
}
});
const app = express();
const httpServer = http.createServer(app);
server.start().then(res=>{
server.applyMiddleware({app, path: '/graphql'});
db.sequelize.sync({force: true}).then(async()=>{
console.info('database synced');
});
httpServer.listen({port: process.env.PORT}, ()=>{
console.info(`Apollo Server is ready at http://localhost:${process.env.PORT}/graphql`)
})
})
}
startApolloServer();
// ./graphql/resolvers/user.js
import { UserInputError } from "apollo-server-express";
import { Op } from "sequelize";
export default {
Query: {
// ! This query is for the logged in user
me: async(root, args, {db, me}, info) => {
const user = await db.user.findByPk(me.id);
return user;
},
// ! This query returns all users
users: async(root, args, {db}, info) => {
const users = await db.user.findAll();
if (!users) throw new Error('No users found')
return users;
}
},
Mutation: {
// ! This mutation creates a new user
createUser: async(root, {input}, {db}) => {
const {email} = input;
const userExists = await db.user.findOne({
where: {
[Op.eq]: [{email}]
}
})
if (userExists) {
throw new Error('A user with this email already exists');
}
const user = await db.user.create({
...input
});
return user;
},
// !
login: async(root, {email, password}, {db}, info) => {
const user = await db.user.findOne({
where: {email},
});
if (!user) throw new UserInputError(`User ${email} does not exist`);
const isValid = await user.validatePassword(password);
if (!isValid) throw new UserInputError(`Password is incorrect`);
return user;
}
}
}
// ./db.js
require('dotenv').config();
import fs from 'fs';
import path from 'path';
import { Sequelize } from 'sequelize';
const basename = path.basename(__filename);
const db = {};
const sequelize = new Sequelize(
process.env.POSTGRES_DB,
process.env.POSTGRES_USER,
process.env.POSTGRES_PASSWORD,
{
host: process.env.POSTGRES_HOST,
port: process.env.POSTGRES_PORT,
dialect: 'postgres'
}
);
sequelize.authenticate()
.then(console.info(()=>'Connection has been established successfully.'))
.catch(e=>console.error('Unable to connect to the database:', e));
const modelPath = path.join(__dirname, '/models');
fs.readdirSync(path.join(modelPath))
.filter((file)=>
file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
)
.forEach((file)=>{
const model = sequelize.define(path.join(modelPath, file));
db[model.name] = model;
});
Object.keys(db).forEach((modelName)=>{
if (db[modelName].associate){
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
export default db;
// ./models/User.js
import bcrypt from 'bcryptjs';
export default (sequelize, DataTypes) => {
const User = sequelize.define(
'user',
{
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: {
args: true,
msg: 'Invalid email'
},
},
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
freezeTableName: true,
},
);
User.findByLogin = async (login) => {
let user = await User.findOne({
where: {email: login},
});
return user;
};
User.beforeCreate(async (user) => {
if (user.password){
user.password = await user.generatePasswordHash();
}
});
User.prototype.updatePasswordHash = async function (password) {
const saltRounds = 10;
return await bcrypt.hash(password, saltRounds);
};
User.prototype.updatePasswordHash = async function () {
const saltRounds = 10;
return await bcrypt.hash(this.password, saltRounds);
};
User.prototype.validatePassword = async function (password) {
return await bcrypt.compare(password, this.password);
};
return User;
}
// ./graphql/typedefs/User.js
import { gql } from "apollo-server-express";
export default gql`
#---------------------------------------
# TYPES
#---------------------------------------
type User {
id: ID
name: String!
email: String!
}
#---------------------------------------
# QUERIES
#---------------------------------------
extend type Query {
me: User
users: [User!]
}
#---------------------------------------
# MUTATIONS
#---------------------------------------
extend type Mutation {
createUser(input: CreateUserInput!): User!
login(email: String!, password: String!): User!
logout: User!
}
#---------------------------------------
# MUTATIONS
#---------------------------------------
input CreateUserInput {
name: String!
email: String!
password: String!
}
`
@ Анатолий Не могли бы вы уточнить «правильно»?
Я имею в виду, что db.user есть реальная модель Sequelize, а email непустая строка.
@Анатолий Спасибо за разъяснения. Да, в db.user есть модель, а адрес электронной почты должен должен быть непустой строкой. значение электронной почты предоставляется во время мутации Apollo Studio; именно эта мутация выдает ошибку.
Ошибка говорит, что db.user не определено в вашем преобразователе.
@robertklep db.user должен быть предоставлен Аполлоном в качестве контекста; см. server.js выше. Я сделал ошибку, предоставив db.user через контекст?
На первый взгляд все выглядит нормально, и если бы db не было указано в контексте, ошибка была бы другой. Работает ли db.user.findOne() за пределами вашего резольвера?
@robertklep console.info(db.user) вроде работает, но console.info(db.user.findOne()) выводит на консоль эту ошибку: TypeError: Cannot read properties of undefined (reading 'findOne'). Очевидно, что findOne() не расположен на db.user. Я пытался явно объявить эту функцию-член в своей модели, однако та же ошибка сохраняется. Разве не следует продолжать автоматически заполнять мои модели этой функцией-членом? (Возможно, я ошибаюсь в этом предположении)
@robertklep Что-то, что усложняет отладку, это, по общему признанию, то, что я не совсем уверен, как отлаживать этот конкретный стек; server.js запускает конечную точку graphql, но не предоставляет никакого клиента, кроме стандартной страницы-заставки Apollo Server, которая ведет к Apollo Studio, и на этой странице не отображаются журналы моей консоли.
Ошибка по-прежнему говорит, что db.user не определено, что не имеет большого смысла, если только это не какой-то прокси или какой-то геттер. Я некоторое время не работал с Sequelize, но я уверен, что Model.findOne() должен существовать. Я бы предложил сократить ваш код до минимума, чтобы проверить, действительно ли настроена ваша база данных (отсюда мой вопрос о работе db.user.findOne вне вашего преобразователя).
Является ли console.info(db.user) show реальным экземпляром модели Sequelize?
@Анатолий А, вы оба правы, db.userявляетсяundefined
Я запустил console.info(db) и получил очень большой объект, затем console.info(db.user) возвращает undefined
И console.info(db.sequelize.models) правильно возвращает путь к файлу объявления модели: { '/Volumes/T7 Touch/Projects/**projectname**/models/User.js': /Volumes/T7 Touch/Projects/**projectname**/models/User.js }
Я думаю, что в db.js есть ошибка (код выше). Похоже, импортируется не модель, а строка дорожка, ведущая к модели.





Вам нужно исправить where вариант из
where: {
[Op.eq]: [{email}]
}
к
where: {
email
}
так же, как вы сделали в мутации login.
Я внес рекомендованное вами изменение в ./graphql/resolvers/user.js, однако ошибка сохраняется.
У вас такая же ошибка в login?
Я только что запустил мутацию login, и возникает та же ошибка, да
Кроме того, кажется, что ваша рекомендация могла работать в более ранней версии, но больше не поддерживается. Я получил следующую ошибку: "message": "Support for {where: 'raw query'} has been removed."
В таком состоянии нет необработанного запроса: where: { email: email}
Мне удалось правильно определить db.user.findOne. Я неправильно использовал sequelize.define() в db.js, и теперь я переписал надоедливый раздел следующим образом:
const modelPath = path.join(__dirname, '/models');
fs.readdirSync(path.join(modelPath))
.filter((file)=>
file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
)
.forEach((file)=>{
const modelFile = path.join(modelPath, file);
const modelExport = require(modelFile);
if (! modelExport) throw new Error ('Error accessing model declaration file: ', modelFile)
const model = modelExport.default(sequelize);
db[model.name] = model;
});
Вы проверили правильность
db.userиemail?