Не удается прочитать свойства undefined (чтение «findOne») | аполлон-сервер-экспресс | продолжать

Меня медленно сводит с ума эта проблема.

Используя локальный экземпляр сервера 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 и email?

Anatoly 01.04.2022 09:25

@ Анатолий Не могли бы вы уточнить «правильно»?

Acea Spades 01.04.2022 10:39

Я имею в виду, что db.user есть реальная модель Sequelize, а email непустая строка.

Anatoly 01.04.2022 10:47

@Анатолий Спасибо за разъяснения. Да, в db.user есть модель, а адрес электронной почты должен должен быть непустой строкой. значение электронной почты предоставляется во время мутации Apollo Studio; именно эта мутация выдает ошибку.

Acea Spades 01.04.2022 10:52

Ошибка говорит, что db.user не определено в вашем преобразователе.

robertklep 01.04.2022 11:45

@robertklep db.user должен быть предоставлен Аполлоном в качестве контекста; см. server.js выше. Я сделал ошибку, предоставив db.user через контекст?

Acea Spades 01.04.2022 12:05

На первый взгляд все выглядит нормально, и если бы db не было указано в контексте, ошибка была бы другой. Работает ли db.user.findOne() за пределами вашего резольвера?

robertklep 01.04.2022 12:36

@robertklep console.info(db.user) вроде работает, но console.info(db.user.findOne()) выводит на консоль эту ошибку: TypeError: Cannot read properties of undefined (reading 'findOne'). Очевидно, что findOne() не расположен на db.user. Я пытался явно объявить эту функцию-член в своей модели, однако та же ошибка сохраняется. Разве не следует продолжать автоматически заполнять мои модели этой функцией-членом? (Возможно, я ошибаюсь в этом предположении)

Acea Spades 01.04.2022 12:59

@robertklep Что-то, что усложняет отладку, это, по общему признанию, то, что я не совсем уверен, как отлаживать этот конкретный стек; server.js запускает конечную точку graphql, но не предоставляет никакого клиента, кроме стандартной страницы-заставки Apollo Server, которая ведет к Apollo Studio, и на этой странице не отображаются журналы моей консоли.

Acea Spades 01.04.2022 13:02

Ошибка по-прежнему говорит, что db.user не определено, что не имеет большого смысла, если только это не какой-то прокси или какой-то геттер. Я некоторое время не работал с Sequelize, но я уверен, что Model.findOne() должен существовать. Я бы предложил сократить ваш код до минимума, чтобы проверить, действительно ли настроена ваша база данных (отсюда мой вопрос о работе db.user.findOne вне вашего преобразователя).

robertklep 01.04.2022 13:34

Является ли console.info(db.user) show реальным экземпляром модели Sequelize?

Anatoly 02.04.2022 16:00

@Анатолий А, вы оба правы, db.userявляетсяundefined

Acea Spades 03.04.2022 22:17

Я запустил console.info(db) и получил очень большой объект, затем console.info(db.user) возвращает undefined

Acea Spades 03.04.2022 22:22

И console.info(db.sequelize.models) правильно возвращает путь к файлу объявления модели: { '/Volumes/T7 Touch/Projects/**projectname**/models/User.js': /Volumes/T7 Touch/Projects/**projectname**/models/User.js }

Acea Spades 03.04.2022 22:32

Я думаю, что в db.js есть ошибка (код выше). Похоже, импортируется не модель, а строка дорожка, ведущая к модели.

Acea Spades 04.04.2022 02:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
15
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вам нужно исправить where вариант из

where: {
  [Op.eq]: [{email}]
}

к

where: {
  email
}

так же, как вы сделали в мутации login.

Я внес рекомендованное вами изменение в ./graphql/resolvers/user.js, однако ошибка сохраняется.

Acea Spades 01.04.2022 11:20

У вас такая же ошибка в login?

Anatoly 01.04.2022 18:15

Я только что запустил мутацию login, и возникает та же ошибка, да

Acea Spades 02.04.2022 02:43

Кроме того, кажется, что ваша рекомендация могла работать в более ранней версии, но больше не поддерживается. Я получил следующую ошибку: "message": "Support for {where: 'raw query'} has been removed."

Acea Spades 04.04.2022 03:41

В таком состоянии нет необработанного запроса: where: { email: email}

Anatoly 04.04.2022 18:13
Ответ принят как подходящий

Мне удалось правильно определить 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;
    });

Другие вопросы по теме