How to implement clean Next.js URL in graphql-yoga - node.js

I am creating a GraphQL app using Next.js for server side rendering. As you might know, there is a recommended way to implement clean URL using Express. I am trying to achieve the same using graphql-yoga but it's not working.
I have tried server.express.get('/route', ...) and server.get('/route', ...) but nothing is working. In the documentation they have given server.express.get(server.options.endpoint, handler()) but it's not working for me.
Has anyone ever implemented clean Next.js URL in a GraphQL Yoga server, and how did you achieve it? Below is how I am creating the server.
function createServer() {
return new GraphQLServer({
typeDefs: "src/schema.graphql",
resolvers: {
Mutation,
Query
},
context: req => ({ ...req, db })
});
}
const server = createServer();
server.express.post('/product/name', (req,res,next) => {
//this is where i want to implement next clean url
//but it's not working
console.log('Am never reached..');
next();
})

With the new versions of Next.js, creating clean URL is straightforward.
Create a parameterised page (e.g. pages/post/[pid].js):
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
const { pid } = router.query
return <p>Post: {pid}</p>
}
export default Post
More info in the Next.js documentation regarding dynamic routes.
You can then link to it and show the desired URL:
<Link href="/post/[pid]" as="/post/abc">
<a>First Post</a>
</Link>

Related

How can I share an authentication state across different test()?

I am trying to use an authentication state session for different test()
However Playwright is always starting a new page instance without using the authentication state "pre defined".
I tried with beforeAll and the storageState and no luck.
import { test, expect } from '#playwright/test';
import { LoginPage } from '#pageRepo/login-page';
import { envConfig } from '#data/env-config';
test.beforeAll(async ({ page }) => {
const loginStep = new LoginPage(page);
await loginStep.uiAuthentication('/', envConfig.userEmail, envConfig.userPassword);
});
test('Please work', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.navigateToUrl('/dashboard');
});
When I run it is visible that the login is made under beforeAll but a new instance is loaded for test('Please work')
Using the storageState instead of beforeAll the json file with the cookies configuration is created but it is never used by the test.
Anyone knows what I am doing wrong?
Make the page instance global so it can be re-used across steps (if that's what you require) e.g.
let loginPage;
// best practise to start from a clean state on each test
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.uiAuthentication('/', envConfig.userEmail, envConfig.userPassword);
});
test('Please work', async ({ page }) => {
// re-use the already created instance
await loginPage.navigateToUrl('/dashboard');
});
Show please code of your storageState.
You need to prepare the Global configuration:
https://playwright.dev/docs/test-advanced#global-setup-and-teardown
Then define global setup in playwright.config.ts
import { defineConfig } from '#playwright/test';
export default defineConfig({
**globalSetup: require.resolve('./global-setup'),**
use: {
baseURL: 'http://localhost:3000/',
**storageState: 'state.json',**
now in globalSetup you need to set authentication credationals by login + pass, or getting token by API.

How to query Sanity client with NestJS?

Right now I have React app initialized with Vite connected with Sanity.
Everything is working just fine, Sanity client is connected, React is fetching data from Sanity, I'm receiving it with no problem.
But the problem is, that if I deploy React app with Sanity connected, then I will leak my projectID and sanity_token to the fronted, which I want to avoid of course.
So I have to make some backend REST API which will be fetched by React, and then my API will fetch Sanity. I could do it with Node.js and Express without problem, but I decided that I will use NestJS and learn something instead.
But when it comes to NestJS, everything is connected a bit different.
On the front I had :
import sanityClient from '#sanity/client';
export const client = sanityClient({
projectId: import.meta.env.VITE_SANITY_PROJECT_ID,
dataset: 'production',
apiVersion: '2022-02-01',
useCdn: true,
token: import.meta.env.VITE_SANITY_TOKEN
});
And for NestJS I found something like this:
import { Injectable } from '#nestjs/common';
import sanityClient, { SanityClient } from '#sanity/client';
#Injectable()
export class SanityService {
public readonly client: SanityClient = sanityClient({
projectId: process.env.SANITY_PROJECT_ID,
dataset: 'production',
apiVersion: '2022-02-01',
useCdn: true,
token: process.env.SANITY_TOKEN
});
}
My question is that if it's a good way to connect Sanity client?
How to query Sanity client with specified GROQ query?
Till now I was using this on the frontend, but it's not gonna work in NestJS:
const query = '*[_type == "blogPost"]';
client.fetch(query)
.then((data) => {
setPosts(data);
})
.catch((err) => {
console.log(err);
})
It turned out that this is the proper way to connect with Sanity client, I had an issue with that similar to this thread
And the solution was the same as in the thread above. Add "esModuleInterop": true to tsconfig.json.
{
"compilerOptions": {
...
"esModuleInterop": true,
...
}
}
Then call for data to sanity client is working properly:
#Get()
async getAllPosts() {
// initialize sanity instance
const sanityService = new SanityService();
// your query
const query = '*[_type == "blogPost"]';
try {
const data = await sanityService.client.fetch(query);
return data;
} catch (error) {
console.log(error.msg);
}
}

passport-jwt using MERN does not shows cookies

I am trying to implement jwt-authentication using nodejs and React by following https://www.youtube.com/playlist?list=PLvTjg4siRgU0HS3cANo7KZ52Wkud083TL.Here they have used react hooks but I want to implement it in class and without redux.But I don't know how to recieve the cookie in the front end by using axios my current code is
Backend:-
user.js
userRouter.post('/login',passport.authenticate('local',{session : false}),(req,res)=>{
if(req.isAuthenticated()){
const {_id,username,role} = req.user;
const token =signToken(_id);
res.cookie('access_token',token,{httpOnly:true,sameSite:true});
res.status(200).json({isAuthenticated :true,user : {username,role}});
}
})
Frontend:-
login.js
onSubmit(e){
e.preventDefault();
const user={
username:this.state.username,
password:this.state.password
}
console.log(user);
axios.post('http://localhost:5000/user/login',user)
.then(res=>{
console.log(res);
if(res.data.isAuthenticated){
console.log("authenticated")
}
})
}
I am about to study this right now so not sure but I have seen you need to set following in the index.js (React part):
axios.defaults.withCredentials = true;
and that's it, the cookie should be saved in the browser.
Have a look here, it might help: https://github.com/eXtremeXR/APIAuthenticationWithNode/blob/master/client/src/index.js

Dynamic GatsbyJS + Node Express deployment

I have a GatsbyJS app to which I added a dynamic side, starting with a contact form using Axios which on POST calls the Express server and sends an email.
Everything is now working locally, but I am having trouble moving to my provider as I am new to backend technology.
Locally, I followed https://www.gatsbyjs.org/docs/client-only-routes-and-user-authentication/ and added the server as a proxy in the package configuration. My host is using cPanel which allows configuring a NodeJS app, which I tried and setup the app but I cannot make it work properly with the front
gatsby-node.js
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// Only update the `/app` page.
if (page.path.match(/^\/app/)) {
// page.matchPath is a special key that's used for matching pages
// with corresponding routes only on the client.
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
ContactForm.js
const handleSubmit = e => {
e.preventDefault()
axios({
method: "POST",
url: "http://websiteaddress.com/post",
data: { name: name, email: email, message: message },
}).then (...)
app.js
const Router = () => {
return (
<MyRouter basepath="/more">
<ContactForm path="/contact" />
</MyRouter>
)
}
server.js is basically using nodemailer and working fine on localhost
My current online Node + static files uploaded to the server render only the static part (the form is not rendered). The Node app is running
How to adapt the configuration (for example the "proxy": "http://localhost:3000" which worked locally) to make it behave on the host as it should?

Setting a test header in GraphQL Playground that I want to use causes "Server cannot be reached"

So I've created a bunch of mutations and queries and stitched them together that works and wanted to introduce authentication into the mix. I added an HTTP Header "x-token" to hold my sign-in token to be able to delete things like their job or the user itself.
const getMe = async req => {
const token = req.headers['x-token'];
if (token) {
try {
return await jwt.verify(token, "notSoSecret");
} catch (e) {
throw new AuthenticationError(
'Your session expired. Sign in again.',
);
}
}
};
const server = new ApolloServer({
typeDefs: schema,
resolvers,
formatError: error => {
// remove the internal sequelize error message
// leave only the important validation error
const message = error.message
.replace('SequelizeValidationError: ', '')
.replace('Validation error: ', '');
return {
...error,
message,
};
},
context: async ({ req }) => {
const me = await getMe(req);
return {
models,
me,
secret: "notSoSecret",
}
},
path: "/graphql"
});
server.applyMiddleware({ app });
sequelize.sync().then(async () => {
createUsersWithJob();
});
app.get("/playground", graphiql({ endpoint: "/graphql" }));
const handler = serverless(app);
export { handler };
const createUsersWithJob = ... //creates seed data
So when I add the token and I look into my command line console, I actually see that I'm setting the header that I want, but it loops over and over again and doesn't stop. Also playground gets an error "Server cannot be reached"
{
"error": "Response not successful: Received status code 400"
}
and running a deleteUser mutation does not work, or any other mutation and query for that matter until I remove the HTTP Header that I set on playground.
There is a secondary issue where everything in this root file runs twice, but that's not as big for me at the moment as the header issue outlined.
If anyone has any insight into this, I'd love to know more. Thanks in advance.
edit: just a quick edit to say that it works fine when I hardcode a pre-existing user.
I had quite a struggle to get the React version of GraphQL Playground working within a very simple html setup, but I figured something out that might help you as well (fingers crossed).
I added a headers section to the config in the GraphQLPlayground.init call, like so:
const root = document.getElementById('root');
GraphQLPlayground.init(root, {
endpoint: "/graphql",
headers: {
"Authorization": "Bearer " + token
}
})
I have an element with an id root since this is embedded in HTML.
Not sure this will help you though, as I just noticed from your code sample you're calling graphiql which is a different GraphQL client than GraphQL Playground..
GraphIQL: https://github.com/skevy/graphiql-app
GraphQL Playground: https://github.com/prisma/graphql-playground

Resources