gpt4 book ai didi

express - 无法在 Next.js 应用程序 : POST body missing 中使用 Apollo-client GraphQL 上传文件

转载 作者:行者123 更新时间:2023-12-04 08:32:02 25 4
gpt4 key购买 nike

我正在尝试使用 Apollo 客户端 + apollo-upload-client 在客户端和 apollo-express-server 在服务器端。

我有下一个错误:

POST body missing. Did you forget use body-parser middleware?

我确定我的服务器上有正文解析器。

Server.ts

import "reflect-metadata";
import "dotenv-safe/config";
import 'module-alias/register';
import { __prod__ } from "@/config/config";
import express from "express";
import Redis from "ioredis";
import session from "express-session";
import connectRedis from "connect-redis";
import { createConnection } from "typeorm";
import { User } from "@/entities/User";
import { Project } from "@/entities/Project";
import path from "path";

const server = async () => {


await createConnection({
type: "postgres",
url: process.env.DATABASE_URL,
logging: true,
migrations: [path.join(__dirname, "./migrations/*")],
entities: [User, Project]
});

const app = express();



require("@/start/logger"); // log exceptions

const RedisStore = connectRedis(session); // connect redis store
const redis = new Redis(process.env.REDIS_URL);

require("@/start/apolloServer")(app, redis); // create apollo server
require("@/start/appConfig")(app,redis,RedisStore) // configure app

const PORT = process.env.PORT || 3007;
app.listen(PORT, () => {
console.log(`🚀 Server Started at PORT: ${PORT}`);
});
};

server().catch((err) => {
console.error(err);
});

我的 Apollo 服务器

我使用 apollo-server-express

import { ApolloServer, gql } from "apollo-server-express";
import { buildSchema } from "type-graphql";
import ProfilePictureResolver from "@/resolvers/upload";
import { createUserLoader } from "@/utils/createUserLoader";
import { UserResolver } from "@/resolvers/user";
import { ProjectResolver } from "@/resolvers/project";
import {Express} from "express";
import { Redis } from "ioredis";

const typeDefs = gql`

scalar Upload

type File {
id: ID!
filename: String!
mimetype: String!
path: String!
}
type Mutation {
singleUpload(file: Upload!): File!
}
`;

module.exports = async function(app:Express,redis:Redis){
const apolloServer = new ApolloServer({
typeDefs,
schema: await buildSchema({
resolvers: [UserResolver, ProjectResolver, ProfilePictureResolver],
validate: false,
}),
context: ({ req, res }) => ({
req,
res,
redis,
userLoader: createUserLoader()
}),
uploads: false
});

apolloServer.applyMiddleware({
app,
cors: false,
});
}

解析器:

import { Resolver, Mutation, Arg } from 'type-graphql'
import { GraphQLUpload, FileUpload } from 'graphql-upload'
import os from 'os'
import { createWriteStream } from 'fs'
import path from 'path'


@Resolver()
export default class SharedResolver {
@Mutation(() => Boolean)
async uploadImage(
@Arg('file', () => GraphQLUpload)
file: FileUpload
): Promise<Boolean> {
const { createReadStream, filename } = await file

const destinationPath = path.join(os.tmpdir(), filename)

const url = await new Promise((res, rej) =>
createReadStream()
.pipe(createWriteStream(destinationPath))
.on('error', rej)
.on('finish', () => {
//stuff to do
})
);

return true;
}
}

服务器配置

import {Express} from 'express'
import { __prod__, COOKIE_NAME } from "@/config/config";
import cors from "cors";
import session from "express-session";
import { Redis } from 'ioredis';
import { RedisStore } from 'connect-redis';
import { bodyParserGraphQL } from 'body-parser-graphql'

module.exports = function(app:Express, redis:Redis, RedisStore:RedisStore){
app.set("trust proxy", 1);
app.use(bodyParserGraphQL());
app.use(
cors({
origin: process.env.CORS_ORIGIN,
credentials: true,
})
);
app.use(
session({
name: COOKIE_NAME,
store: new RedisStore({
client: redis,
disableTouch: true,
}),
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: "lax",
secure: __prod__,
domain: __prod__ ? ".heroku.com" : undefined,
},
saveUninitialized: false,
secret: process.env.SESSION_SECRET,
resave: false,
})
);
}

客户端

应用客户端

import { createWithApollo } from "@/utils/createWithApollo";
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { NextPageContext } from "next";
import { createUploadLink } from 'apollo-upload-client';

const createClient = (ctx: NextPageContext) =>
new ApolloClient({
credentials: "include",
headers: {
cookie:
(typeof window === "undefined"
? ctx?.req?.headers.cookie
: undefined) || "",
},

cache: new InMemoryCache({
typePolicies: {
Query: {}
}
}),
link: createUploadLink({uri:'http://localhost:4000/graphql'})

});

// const createClient: ApolloClient<NormalizedCacheObject> = new ApolloClient({
// cache: new InMemoryCache({}),
// uri: 'http://localhost:4000/graphql'
// });


export const withApollo = createWithApollo(createClient);

查询

import { gql } from '@apollo/client';

export const UPLOAD_IMAGE_MUTATION = gql`
mutation uploadImage($file: Upload!) {
uploadImage(file: $file)
}
`;

页面

import React, {useState} from 'react';
import {useSelector} from "react-redux";
import {Box} from "@/components/UI/Box/Box"
import {Header} from "@/components/UI/Text/Header"
import { withApollo } from "@/utils/withApollo";
import withPrivateRoute from "@/HOC/withPrivateRoute";
import { useMutation } from "@apollo/react-hooks";
import { UPLOAD_IMAGE_MUTATION } from "@/graphql/mutations/uploadImage";

interface IProps{};

const Profile:React.FC<IProps> = () => {

const user = useSelector(state => state.user);
const [file, setFileToUpload] = useState(null);
const [uploadImage, {loading}] = useMutation(UPLOAD_IMAGE_MUTATION);

const onAvatarUpload = (e) =>{
setFileToUpload(e.target.files[0]);
}

const onSubmit = async (e) =>{
e.preventDefault();
const response = await uploadImage({
variables: {file}
});


}

return (
<Box mt={20} pl={30} pr={30}>
<Header>
Edit Profile
</Header>
<input onChange={onAvatarUpload} type="file" placeholder="photo" />
<button onClick={(e)=>onSubmit(e)}>Submit</button>
</Box>

)
};

export default withApollo({ ssr: false })(withPrivateRoute(Profile, true));

我的客户端包:

{
"name": "app",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@apollo/client": "^3.2.5",
"@apollo/react-hooks": "^4.0.0",
"apollo-upload-client": "^14.1.3",
"graphql": "^15.4.0",
"graphql-tag": "^2.11.0",
"graphql-upload": "^11.0.0",
"isomorphic-unfetch": "^3.1.0",
"next": "^9.5.5",
"next-apollo": "^5.0.3",
"next-redux-wrapper": "^6.0.2",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-is": "^16.13.1",
"react-redux": "^7.2.2",
"redux": "^4.0.5",
"styled-components": "^5.2.1",
"urql": "^1.10.3",
"uuid": "^8.3.1"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.11.5",
"@testing-library/react": "^11.1.1",
"@types/graphql": "^14.5.0",
"@types/jest": "^26.0.15",
"@types/next": "^9.0.0",
"@types/node": "^14.0.27",
"@types/react": "^16.9.55",
"@types/react-dom": "^16.9.9",
"@types/styled-components": "^5.1.4",
"@types/uniqid": "^5.2.0",
"@types/uuid": "^8.3.0",
"@welldone-software/why-did-you-render": "^5.0.0",
"babel-plugin-inline-react-svg": "^1.1.2",
"babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-styled-components": "^1.11.1",
"redux-devtools-extension": "^2.13.8",
"typescript": "^4.0.5"
}
}

服务器包:

{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "server.ts",
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"nodemon": "nodemon dist/server.js",
"dev": "npm-run-all --parallel watch nodemon",
"start": "ts-node src/server.ts",
"client": "cd ../ && npm run dev --prefix client",
"runall": "npm-run-all --parallel client dev",
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
"migration:up": "typeorm migration:run",
"migration:down": "typeorm migration:revert",
"migration:generate": "typeorm migration:generate -n 'orm_migrations'"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"apollo-server-express": "^2.16.1",
"argon2": "^0.26.2",
"connect-redis": "^5.0.0",
"cors": "^2.8.5",
"dataloader": "^2.0.0",
"dotenv-safe": "^8.2.0",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-session": "^1.17.1",
"graphql": "^15.3.0",
"ioredis": "^4.17.3",
"module-alias": "^2.2.2",
"path": "^0.12.7",
"pgtools": "^0.3.0",
"reflect-metadata": "^0.1.13",
"type-graphql": "^1.0.0-rc.3",
"typeorm": "^0.2.25",
"uuid": "^8.3.0",
"winston": "^3.3.3"
},
"devDependencies": {
"@types/connect-redis": "0.0.14",
"@types/cors": "^2.8.8",
"@types/express": "^4.17.8",
"@types/express-session": "^1.17.0",
"@types/graphql": "^14.5.0",
"@types/ioredis": "^4.17.7",
"@types/node": "^8.10.66",
"@types/nodemailer": "^6.4.0",
"@types/pg": "^7.14.6",
"@types/uuid": "^8.3.0",
"gen-env-types": "^1.0.4",
"nodemon": "^2.0.6",
"npm-run-all": "^4.1.5",
"pg": "^8.4.2",
"ts-node": "^8.10.2",
"typescript": "^3.9.7"
},
"_moduleAliases": {
"@": "dist/"
}
}

注意!

当我尝试从 apolloServer 配置中删除 uploads: false 时,我收到另一个错误:

"Variable "$file" got invalid value {}; Upload value invalid."

确实在我看到的表单数据中

------WebKitFormBoundarybNufV7QLX3EU1SN6 Content-Disposition: form-data; name="operations"

{"operationName":"uploadImage","variables":{"file":null},"query":"mutationuploadImage($file: Upload!) {\n uploadImage(file: $file)\n}\n"}------WebKitFormBoundarybNufV7QLX3EU1SN6 Content-Disposition: form-data; name="map"

{"1":["variables.file"]}------WebKitFormBoundarybNufV7QLX3EU1SN6 Content-Disposition: form-data; name="1"; filename="Screen Shot 2020-11-20 at 17.56.14.png"Content-Type: image/png

------WebKitFormBoundarybNufV7QLX3EU1SN6--

我 100% 确定我通过了文件。

最佳答案

我在我的 NextJs 项目中遇到了同样的问题,我发现 Upload 的解析器检查值是否为 instanceOf Upload,这在某种程度上不起作用。

我通过创建自己的解析器来修复它,而不是像这样使用“graphql-upload”包:

解决方案 1:

export const resolvers: Resolvers = {
Upload: new GraphQLScalarType({
name: 'Upload',
description: 'The `Upload` scalar type represents a file upload.',
parseValue(value) {
return value;
},
parseLiteral(ast) {
throw new GraphQLError('Upload literal unsupported.', ast);
},
serialize() {
throw new GraphQLError('Upload serialization unsupported.');
},
})
};

解决方案 2:

或者您可以不为此类型声明任何解析器。


注意:请确保您在架构中声明了标量类型的上传,并且您需要将上传字段添加到您的 Apollo 服务器配置中:

const apolloServer = new ApolloServer({
uploads: {
maxFileSize: 10000000, // 10 MB
maxFiles: 20
},
.
.
.

关于express - 无法在 Next.js 应用程序 : POST body missing 中使用 Apollo-client GraphQL 上传文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64971480/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com