How to configure NextJS Server API GraphQL Subscription - reactjs

currently i'm learning how to develop an application using graphql.
I'm facing the error of implementing the subscription inside my application
resolver.js
const pubsub = new PubSub()
export const resolvers = {
Mutation: {
createCategory:async(parent,arg,{pubsub}) => {
console.log(pubsub)
const prisma = new PrismaClient();
const {name} = arg
await prisma.category.create({
data:{
name:name,
author:{
connect:{
id: "vjuwU0KvWhVDehB1gPgJ7TFRwcy1"
}
}
}
});
pubsub.publish('NEW_CATEGORY',{
newCategory:{
id:"1"
}
})
return true;
}
},
Subscription:{
category:{
subscribe: (_,__,)=>pubsub.asyncIterator(['NEW_CATEGORY']),
}
}
};
api/graphql.js
const pubsub = new PubSub()
const apolloServer = new ApolloServer({ typeDefs, resolvers,context:({req,res})=>({req,res,pubsub})});
const startServer = apolloServer.start();
export default cors(async function handler(req, res) {
if (req.method === "OPTIONS") {
res.end();
return false;
}
await startServer;
await apolloServer.createHandler({
path: "/api/graphql",
})(req, res);
});
export const config = {
api: {
bodyParser: false,
},
};
Error were occurred, i was unable to solve it due to this error message
error - image

Related

Apollo client fetchMore not updating data cache?

I am try to use #apollo/client in my nextjs application (only frontend). My backend is done using nestjs-graphql. I am facing one problem using #apollo/client.
Here is my code-
client.ts
//Initialize apolloClient
let apolloClient: ApolloClient<NormalizedCacheObject>;
//Creating link
function createIsomorphLink() {
return new HttpLink({
uri: "http://localhost:5000/piechat",
credentials: 'same-origin'
})
}
//Create apollo client
function createApolloClient() {
return new ApolloClient({
ssrMode: true,
link: createIsomorphLink(),
cache: new InMemoryCache()
})
}
//Initialize Apollo
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
if (initialState) {
const existingCache = _apolloClient.extract()
const data = merge(initialState, existingCache)
_apolloClient.cache.restore(data)
}
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
//Use Apollo
export function useApollo(initialState = null) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
Then in nextjs getServerSideProps-
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const apolloClient = initializeApollo()
await apolloClient.query({
query: GET_CONVERSATION_LIST,
context: { headers: { authorization: `Bearer ${ctx.req.cookies["session"]}` } },
errorPolicy: "all"
})
return {
props: {
initialApolloState: apolloClient.cache.extract(),
}
}
}
Then in one conversation.tsx(I use this data)-
const { data } = useQuery<GetConversationData>(GET_CONVERSATION_LIST);
On the other component (createConver.tsx) I use fetchMore function-
const { fetchMore } = useQuery(GET_CONVERSATION_LIST)
useEffect(() => {
if (data) { //If new data creation is success
fetchMore({
context: { headers: { authorization: `Bearer ${getCookie("session")}` } }, updateQuery(_, { fetchMoreResult }) {
return fetchMoreResult
}
})
}
}, [data])
Everything is well. But when I refresh my page, I see that new data not comes. please see this gif-
But I am not understanding what is the problem. I am not getting any docs or any things else. Please help me anyone.

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client in nextJS

The code works properly and redirects to the stripe checkout page but after deployment, it doesn't. I'm getting the status 500 when trying to checkout but the cart items and amount get posted in stripe logs with the status unpaid.
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:387:5)
at ServerResponse.setHeader (node:_http_outgoing:603:11)
at NodeNextResponse.setHeader (C:\Users\subash\OneDrive\Desktop\ecommerce\ecommerce\node_modules\next\dist\server\base-http\node.js:56:19)
Here's the code
lib/getStripe.js
import {loadStripe} from '#stripe/stripe-js';
let stripePromise;
const getStripe = () => {
if(!stripePromise){
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
}
return stripePromise;
}
export default getStripe;
cart.js
const handleCheckout = async () => {
const stripe = await getStripe();
const response = await fetch('/api/stripe', {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(cartItems)
});
if (response.statusCode === 500) return;
const data = await response.json();
toast.loading("Redirecting...");
stripe.redirectToCheckout({ sessionId: data.id });
};
pages/api/stripe.js
import Stripe from 'stripe';
const stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY);
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
// Create Checkout Sessions from body params.
const params = {
submit_type :'pay',
mode:'payment',
payment_method_types:['card'],
shipping_address_collection: {
allowed_countries: ['IN'],
},
shipping_options: [
{shipping_rate: '...'}
],
line_items: req.body.map((item)=>{
const img = item.image[0].asset._ref;
const newImage = img.replace('image-','https://cdn.sanity.io/..../').replace('-webp','.webp');
return {
price_data:{
currency:'inr',
product_data:{
name:item.name,
images:[newImage],
},
unit_amount:item.price*100,
},
adjustable_quantity:{
enabled:true,
minimum:1
},
quantity:item.quantity
}
}),
success_url: `${req.headers.origin}/success`,
cancel_url: `${req.headers.origin}`,
}
const session = await stripe.checkout.sessions.create(params);
res.status(200).json(session);
res.redirect(303, session.url);
} catch (err) {
res.status(err.statusCode || 500).json(err.message);
}
} else {
res.setHeader('Allow', 'POST');
console.log("error");
res.status(405).end('Method Not Allowed');
}
}
You're still using the legacy Checkout integration with redirectToCheckout instead you should look at the new integration path for Next.js that you can find here. If you want more info about migrating from the legacy integration you can check the Checkout migration guide here.

How to handle error format in Redux-toolkit rtk-query graphql application

I'm developing an application based on redux-toolkit rtk-query and graphql.
I use graphql-codegen to generate the reducers starting from the graphql schema and everything working as expected.
Now i have a problem to handle errors. Has i understand redux-toolkit raise custom error with a specific format like this
{
name: "Error",
message: "System error",
stack:
'Error: System error: {"response":{"errors":[{"message":"System error","locations":[{"line":3,"column":3}],"path":["completaAttivita"],"extensions":{"errorCode":505,"classification":"VALIDATION","errorMessage":"Messaggio di errore","verboseErrorMessage":"it.cmrc.sid.backend.exception.CustomException: I riferimenti contabili non sono piĆ¹ validi","causedBy":"No Cause!"}}],"data":{"completaAttivita":null},"status":200,"headers":{"map":{"content-length":"398","content-type":"application/json"}}},"request":{"query":"\\n mutation completaAttivita($taskName: TipoAttivita, $taskId: String, $determinaId: BigInteger, $revisione: Boolean, $nota: NotaInputInput, $avanzaStatoDetermina: Boolean, $attribuzioniOrizzontali: AttribuzioniOrizzontaliInputInput, $firmaInput: FirmaInputInput, $roles: [String]) {\\n completaAttivita(\\n taskName: $taskName\\n taskId: $taskId\\n determinaId: $determinaId\\n revisione: $revisione\\n nota: $nota\\n avanzaStatoDetermina: $avanzaStatoDetermina\\n attribuzioniOrizzontali: $attribuzioniOrizzontali\\n firmaInput: $firmaInput\\n roles: $roles\\n ) {\\n id\\n }\\n}\\n ","variables":{"taskId":"24ac495b-46ca-42f4-9be2-fd92f0398114","determinaId":1342,"taskName":"firmaDirigente","firmaInput":{"username":"fdfs","password":"fdsf","otp":"fdsdf"}}}}\n at eval (webpack-internal:///../../node_modules/graphql-request/dist/index.js:354:31)\n at step (webpack-internal:///../../node_modules/graphql-request/dist/index.js:63:23)\n at Object.eval [as next] (webpack-internal:///../../node_modules/graphql-request/dist/index.js:44:53)\n at fulfilled (webpack-internal:///../../node_modules/graphql-request/dist/index.js:35:58)'
};
But my graphql endpoint return this
{
errors: [
{
message: "System error",
locations: [{ line: 3, column: 3 }],
path: ["completaAttivita"],
extensions: {
errorCode: 505,
classification: "VALIDATION",
errorMessage: "Messaggio di errore",
verboseErrorMessage:
"it.cmrc.sid.backend.exception.CustomException: Messaggio di errore",
causedBy: "No Cause!"
}
}
],
data: { completaAttivita: null }
};
Using rtk-query and the autogenerated client i have no access to the complete response from server.
And i need to extract the error messagge in the exceptions object.
From redix-toolkit documentation i understand that i need to catch the error and call rejectwithvalue() from a createAsyncThunk but i dont'undertand of to do that.
Here the base api object
import { createApi } from '#reduxjs/toolkit/query/react';
import { graphqlRequestBaseQuery } from './base-request';
import { GraphQLClient } from 'graphql-request';
import { getSession } from 'next-auth/react';
export const client = new GraphQLClient(
`${process.env.NEXT_PUBLIC_API_URL}/graphql`,
{
credentials: 'same-origin',
headers: {
Accept: 'application/json'
}
}
);
export const api = createApi({
baseQuery: graphqlRequestBaseQuery({
client,
prepareHeaders: async (headers, { getState }) => {
const session = await getSession();
if (session) {
headers.set('Authorization', `Bearer ${session?.access_token}`);
}
return headers;
}
}),
endpoints: () => ({}),
refetchOnMountOrArgChange: true
});
Thanks to #phry for merge my solution.
#rtk-query/graphql-request-base-query (version > 2.1.0) introduce a new configuration to handle errors format. Here a small explanation.
Typization
graphqlRequestBaseQuery<CustomErrorFormat>
Custom Error handler
...
customErrors: (props: ClientError) => CustomErrorFormat
...
Full example https://codesandbox.io/s/headless-microservice-uzujqb?file=/src/App.tsx
import { createApi } from '#reduxjs/toolkit/query/react';
import { graphqlRequestBaseQuery } from '#rtk-query/graphql-request-base-query';
import { ClientError, GraphQLClient } from 'graphql-request';
import { getSession } from 'next-auth/react';
export const client = new GraphQLClient(
`${process.env.NEXT_PUBLIC_API_URL}/graphql`,
{
credentials: 'same-origin',
headers: {
Accept: 'application/json'
}
}
);
export const api = createApi({
baseQuery: graphqlRequestBaseQuery<
Partial<ClientError & { errorCode: number }>
>({
client,
prepareHeaders: async (headers, { getState }) => {
const session = await getSession();
if (session) {
headers.set('Authorization', `Bearer ${session?.access_token}`);
}
return headers;
},
customErrors: ({ name, stack, response }) => {
const { errorMessage = '', errorCode = 500 } = response?.errors?.length
? response?.errors[0]?.extensions
: {};
return {
name,
message: errorMessage,
errorCode,
stack
};
}
}),
endpoints: () => ({}),
refetchOnMountOrArgChange: true
});
You can always write a wrapper around your baseQuery to reformat it:
const originalBaseQuery = graphqlRequestBaseQuery(...)
const wrappedBaseQuery = async (...args) => {
const result = await originalBaseQuery(...args);
if (result.error) {
// modify `result.error` here however you want
}
return result
}
It could also be necessary that you need to try..catch for that:
const originalBaseQuery = graphqlRequestBaseQuery(...)
const wrappedBaseQuery = async (...args) => {
try {
return await originalBaseQuery(...args);
} catch (e) {
// modify your error here
return { error: e.foo.bar }
}
}
I think this just slipped by when I was writing graphqlRequestBaseQuery and so far nobody has asked about it. If you have found a nice pattern of handling this, a pull request against graphqlRequestBaseQuery would also be very welcome.

Dynamic generated sitemap -there are only declared pages on sitemap

I'd like to generate dynamic url fo each slug, but there is an array only with pages which I declared: const pages = ["/", "/about", "/portfolio", "/blog"];
http://localhost:3000/api/my-sitemap. I've installed npm sitemap from https://www.npmjs.com/package/sitemap
my query in ../../lib/data.js
export const getBlogSlugs = async () => {
const endpoint =
"https://api-eu-central-gsagasgasxasasxsaxasxassaster";
const graphQLClient = new GraphQLClient(endpoint);
const query = gql`
{
posts {
slug
}
}
`;
return await graphQLClient.request(query);
};
pages/api/my-sitemap.js
import { getBlogSlugs } from "../../lib/data";
const { SitemapStream, streamToPromise } = require("sitemap");
const { Readable } = require("stream");
export const getStaticProps = async () => {
const { posts } = await getBlogSlugs();
return {
props: {
posts,
},
};
};
export default async (req, res, posts) => {
try {
const links = [];
posts?.map((slug) => {
links.push({
url: `/blog/${slug}`,
changefreq: "daily",
priority: 0.9,
});
});
// Add other pages
const pages = ["/", "/about", "/portfolio", "/blog"];
pages.map((url) => {
links.push({
url,
changefreq: "daily",
priority: 0.9,
});
});
// Create a stream to write to
const stream = new SitemapStream({
hostname: `https://${req.headers.host}`,
});
res.writeHead(200, {
"Content-Type": "application/xml",
});
const xmlString = await streamToPromise(
Readable.from(links).pipe(stream)
).then((data) => data.toString());
res.end(xmlString);
} catch (e) {
console.log(e);
res.send(JSON.stringify(e));
}
};
I added to my robots.txt in pudblic folder:
User-agent: *
Allow: /
Sitemap: http://localhost:3000/api/my-sitemap
What I got is only declared pages
localhost:3000/api/my-sitemap
I tried like this and doesn't work too:
export const getStaticProps = async () => {
const data = await getBlogSlugs();
return {
props: {
posts: data.posts,
},
};
};
export default async (req, res, posts) => {
try {
const links = [];
posts?.map((post) => {
links.push({
url: `/blog/${post.slug}`,
changefreq: "daily",
priority: 0.9,
});
});
You cannot use getStaticProps from an API route.
https://github.com/vercel/next.js/discussions/16068#discussioncomment-703870
You can fetch the data directly inside the API function.
Edit: In my app, I use the API route code below to fetch data from external server
import fetch from "isomorphic-unfetch";
export default async (req, res) => {
try {
const result = await fetch("YOUR_URL");
const posts = await result.json();
//use posts
});
} catch (e) {}
};
For GraphQl may be you can check the example given in vercel site
https://github.com/vercel/next.js/tree/canary/examples/api-routes-graphql

Apollo Subscriptions not listening to new data on client

Previously my apollo setup was listening to subscriptions until I added in socket.io and now my client setup is no longer listening to new data. my server codes seem to be ok based on my testing using graphql playground.
in my browser console, i get the following error message
client.js:652 WebSocket connection to 'ws://localhost:4000/' failed: Error during WebSocket handshake: Unexpected response code: 400
There seems to be some issue with my client side setup to use apollo subscriptions.
Appreciate any pointers? Thanks in advance
import { ApolloClient } from "apollo-client";
import { onError } from "apollo-link-error";
import { ApolloLink, split } from "apollo-link";
import { createUploadLink } from "apollo-upload-client";
import gql from "graphql-tag";
import { withClientState } from "apollo-link-state";
import { InMemoryCache } from "apollo-cache-inmemory";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";
import { setContext } from "apollo-link-context";
const cache = new InMemoryCache();
const defaultState = {
currentGame: {
__typename: "currentGame",
teamAScore: 0,
teamBScore: 0,
teamAName: "EAGLES",
teamBName: "LOL"
}
};
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
Mutation: {
updateGame: (_, { index, value }, { cache }) => {
const query = gql`
query GetCurrentGame {
currentGame #client {
teamAScore
teamBScore
teamAName
teamBName
}
}
`;
const previous = cache.readQuery({ query });
const data = {
currentGame: {
...previous.currentGame,
[index]: value
}
};
cache.writeQuery({ query, data });
return null;
},
resetCurrentGame: (_, d, { cache }) => {
cache.writeData({ data: defaultState });
}
}
}
});
const host = "http://localhost:4000";
// httpLink
const httpLink = createUploadLink({
uri: `${host}/graphql`,
credentials: "same-origin"
});
// wsLink
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000/`,
options: {
reconnect: true
}
});
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const webLink = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httpLink
);
// authMiddleware
const authLink = setContext(async (req, { headers }) => {
// const token = await AsyncStorage.getItem("#token");
const token = "";
return {
headers: {
...headers,
authorization: token ? `${token}` : ""
}
};
});
const errorLink = onError(({ networkError, graphQLErrors }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
}
if (networkError) console.log(`[Network error]: ${networkError}`);
});
export const client = new ApolloClient({
link: ApolloLink.from([authLink, stateLink, errorLink, webLink]),
cache
});
My server side code if required
//! Using Apollo Server Express
const app = express();
const path = "/graphql";
const schema = genSchema();
export const startServer = async () => {
const server = new ApolloServer({
schema,
context: ({ req }: any) => ({
req,
pubsub,
userLoader: userLoader()
})
});
app.use(cors());
app.use(authMiddleware);
app.use("/images", express.static("images"));
app.use(
"graphql",
graphqlUploadExpress({
uploadDir: "/",
maxFileSize: 100000000,
maxFiles: 10
}),
graphqlHTTP({ schema }) as any
);
server.applyMiddleware({ app, path });
//! Added Subscription Handler
const httpServer = createServer(app);
server.installSubscriptionHandlers(httpServer);
const port = process.env.PORT || 4000;
await createConnection();
await httpServer.listen({
port
});
console.log(`Server is running on localhost:${port}${server.graphqlPath}`);
};
Rectified. My client side apollo setup should point to ws://localhost:4000/graphql and not just ws://localhost:4000/
// wsLink
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000/graphql`,
options: {
reconnect: true
}
});

Resources