Directly import next.js API endpoint in getServerSideProps() - reactjs

When fetching data using getServerSideProps() in Next.js, they recommend directly importing the API endpoint instead of using fetch() and running another HTTP request. This makes sense, and I was able to get it working until implemented middleware for my API (note, I'm using the API feature built into Next.js). Now with middleware implemented, I can't export functions that use the middleware, I have to export the handler. See below:
const handler = nextConnect();
handler.use(middleware);
handler.get(async (req, res) => {
const post = await req.db.collection("posts").findOne();
res.send({
post: post,
});
});
export default handler;
What would be the recommend way to import my API endpoint into getServerSideProps? I would like to do something as follows, but the getPost() function no longer has access to the database middleware:
export const getPost = async () => {
const post = await req.db.collection("posts").findOne();
return post;
}
handler.get(async (req, res) => {
res.send({
post: getPost(),
});
});
and then in my next.js page:
import { getPost } from './api/post';
...
export async function getServerSideProps(context) {
return {
props: {
post: getPost(),
}
}
}

In any case, you'll have to pass the req and res objects to the function. But if you do the following, the post prop should be populated with a NextApiResponse instance, which at it's base is a stream.Writable object, which is probably not what you want...
import { getPost } from './api/post';
...
export async function getServerSideProps({req, res}) {
return {
props: {
post: await getPost(req, res),
}
}
}
You could try to read the stream, but that seems like more trouble than refactoring your code, but if you call getPost(req, res).end(), I think you should get the streamed data, but I'm not sure how it will be formatted. You'd have to check.
You could split your functions up a little more..
// In your api endpoint:
const handler = nextConnect();
handler.use(middleware);
export async function getPostMethod(db) {
return await db.collection("posts").findOne();
}
handler.get(async (req, res) => {
res.send({
post: await getPostMethod(req, res, req.db)
})
});
export default handler;
// In your page component:
export async function getServerSideProps({req, res}) {
// Do what ever you have to do here to get your database connection
const db = await whereIsMyDb()
return {
props: {
post: await getPostMethod(db),
}
}
}

Related

NextJs 13(app dir) fetch data from build in api

I am using Nextjs 13 with /src and /app directory. Below I am trying to fetch data from nextjs api:
//src/app/page.tsx
const getProducts = async () => {
try {
const res = await fetch('/api/products');
const data = await res.json();
return data;
} catch (err) {
console.log(err);
}
}
export default async function Home() {
....
}
//src/pages/api/products
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Product[]>
) {
res.status(200).json(products)
}
this doesn't work and instead show Failed to parse URL from /api/products and TypeError [ERR_INVALID_URL]: Invalid URL.
Note: When I fetch the same data using localhost:3000 with url that does work perfectly fine.
I even tried using `/pages/api/products' that doesn't work either.
please create index.js inside /api/products/index.js and then build your endpoint and call that from Component as you did above
e.g
export default function handler(req, res) {
res.status(200).json([{id:1, title:'T-Shirt'},{id:2,title:'Shoes'}]);
}

Intercepting Auth0 getSession with MSW.js and Cypress

I'm building NextJS app with SSR. I've written the getServerSideProps function that makes a call to supabase. Before making the call I'm trying to get user session by calling getSession function from #auth0/nextjs-auth0 package.
I'm trying to mock it in the handlers.ts file:
import { rest } from 'msw';
export const handlers = [
// this is the endpoint called by getSession
rest.get('/api/auth/session', (_req, res, ctx) => {
return res(ctx.json(USER_DATA));
}),
rest.get('https://<supabase-id>.supabase.co/rest/v1/something', (_req, res, ctx) => {
return res(ctx.json(SOMETHING));
}),
];
My mocks file: requestMocks/index.ts:
export const initMockServer = async () => {
const { server } = await import('./server');
server.listen();
return server;
};
export const initMockBrowser = async () => {
const { worker } = await import('./browser');
worker.start();
return worker;
};
export const initMocks = async () => {
if (typeof window === 'undefined') {
console.log('<<<< setup server');
return initMockServer();
}
console.log('<<<< setup browser');
return initMockBrowser();
};
initMocks();
Finally, I'm calling it in the _app.tsx file:
if (process.env.NEXT_PUBLIC_API_MOCKING === 'true') {
require('../requestMocks');
}
Unfortunately, it does work for me. I'm getting no user session data in the getServerSideProps function in my page component:
import { getSession } from '#auth0/nextjs-auth0';
export const getServerSideProps = async ({ req, res }: { req: NextApiRequest; res: NextApiResponse }) => {
const session = getSession(req, res);
if (!session?.user.accessToken) {
// I'm constantly falling here
console.log('no.session');
return { props: { something: [] } };
}
// DO something else
};
Any suggestions on how to make it working in Cypress tests would be great.
I'm expecting that I will be able to mock requests made in getServerSideProps function with MSW.js library.
I made it finally. Looks like I don't have to mock any calls. I need to copy my user appSession cookie and save it in cypress/fixtures/appSessionCookie.json file:
{
"appSession": "<cookie-value>"
}
Then use it in tests as follows:
before(() => {
cy.fixture('appSessionCookie').then((cookie) => {
cy.setCookie('appSession', cookie.appSession);
});
});
This makes a user automatically logged in with Auth0.

How to get post data in NextJS with Typescript?

I wrote a function to get posted form data in a NextJs page. While this works, the req and res parameters aren't typed:
const getBody = promisify(bodyParser.urlencoded());
export async function getServerSideProps({ req, res }) {
if (req.method === "POST") {
await getBody(req, res);
}
return {
props: {
input: req.body?.input
}
}
}
I've tried using req: NextApiRequest, res: NextApiResponse with an interface but the function body won't work:
interface ExtendedNextApiRequest extends NextApiRequest {
body: {
input: string;
};
}
export async function getServerSideProps(req: ExtendedNextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
await getBody(req, res);
}
return {
props: {
input: req.body?.input
}
}
}
However I get a serialization error:
Error: Error serializing `.input` returned from `getServerSideProps` in "/my-page".
Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.
Your problem is that you are using getServerSideProps as an API endpoint. getServerSideProps is meant to be be used to fetch data from an API endpoint or backend logic and pass it to the function component, so it can not handle post methods. If you want to make an API endpoint, you can make a function in the /pages/api directory. You can strong type that like this:
import type { NextApiHandler } from "next";
let handler: NextApiHandler<any> = async (req, res) => {
// your code goes here
};
export default handler;
where the any can be replaced by the type of the api response.

Next Auth getSession not working in api routes

So basically I use getServerSideProps to call some APIs. when I call getSession in getServerSideProps() I get a valid object.
export async function getServerSideProps({ req }) {
const session = await getSession({ req }); // works
But when I call it in the API that is called in that getServerSideProps() function, I get null.
import { getSession } from "next-auth/react";
export default async (req, res) => {
const { db } = await connectToDatabase();
const session = await getSession({ req }); // returns null
Here is NextAuth documentation for reference:
This is very late, but I found the section in the docs where you can get the appropriate session object in API in this section.
Using unstable_getServerSession()
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
export default async (req, res) => {
const session = await unstable_getServerSession(req, res, authOptions)
if (session) {
// Signed in
console.log("Session", JSON.stringify(session, null, 2))
} else {
// Not Signed in
res.status(401)
}
res.end()
}
Using getToken()
// This is an example of how to read a JSON Web Token from an API route
import { getToken } from "next-auth/jwt"
export default async (req, res) => {
// If you don't have NEXTAUTH_SECRET set, you will have to pass your secret as `secret` to `getToken`
const token = await getToken({ req })
if (token) {
// Signed in
console.log("JSON Web Token", JSON.stringify(token, null, 2))
} else {
// Not Signed in
res.status(401)
}
res.end()
}
The most important part is to pass the authOptions that is imported from /api/[...nextauth]
NOTE: getSession is a client API, as in it will only work on getStaticProps

FormData with NextJS API

Background
I am trying to create a simple CRUD application using NextJS along with react-redux, so what it does is that it saves peoples contacts.So when adding a contact i am trying to send some data along with a file to a NextJS API.
Issue
ContactAction.js
Make a POST request from redux action to add a contact
export const addContact = (data) => async (dispatch) => {
try {
var formData=new FormData();
formData.append('name',data.Name);
formData.append('email',data.Email);
formData.append('phone',data.Phone);
formData.append('image',data.Image);
let response= await Axios.post(`http://localhost:3000/api/contact/addContact`,formData,{
headers:{
'x-auth-token':localStorage.getItem('token')
}
});
} catch (error) {
console.log(error);
}
}
addContact.js
This is the API route in /api/contact/
const handler = async (req, res) => {
switch(req.method){
case "POST":{
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body);
// do some stuff here and send response
}
this is what i get in the terminal after the log,also the file is Gibberish as well when logging req.files
Current Effort
I tried using third party packages such as formidable and formidable-serverless but got no luck. so after a day i made it work with a package called multiparty.
addContact.js
const handler = async (req, res) => {
switch(req.method){
case "POST":{
let form = new multiparty.Form();
let FormResp= await new Promise((resolve,reject)=>{
form.parse(req,(err,fields,files)=>{
if(err) reject(err)
resolve({fields,files})
});
});
const {fields,files} = FormResp;
req.body=fields;
req.files=files;
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body); //Now i get an Object which i can use
// do some stuff here and send response
}
The above solution is obviously redundant and probably not the best way to go about it plus i don't want to add these 7 8 lines into each route.
so if someone could help me understand what i am doing wrong and why formData doesn't seem to work with NextJS API (when it works with the Express server) i would be grateful.
FormData uses multipart/form-data format. That is not a simple POST request with a body. It is generally used for uploading files, that's why it needs special handling. As an alternative, you could use JSON.
Here is my solution, i hope this helps anybody.
First of all you need to install next-connect and multer as your dependencies.
Now you can use this API route code.
import nextConnect from "next-connect";
import multer from "multer";
const apiRoute = nextConnect({
onError(error, req, res) {
res.status(501).json({ error: `Sorry something Happened! ${error.message}` });
},
onNoMatch(req, res) {
res.status(405).json({ error: `Method "${req.method}" Not Allowed` });
},
});
apiRoute.use(multer().any());
apiRoute.post((req, res) => {
console.log(req.files); // Your files here
console.log(req.body); // Your form data here
// Any logic with your data here
res.status(200).json({ data: "success" });
});
export default apiRoute;
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
Here is an example about uploading file with Next.js:
https://codesandbox.io/s/thyb0?file=/pages/api/file.js
The most important code is in pages/api/file.js
import formidable from "formidable";
import fs from "fs";
export const config = {
api: {
bodyParser: false
}
};
const post = async (req, res) => {
const form = new formidable.IncomingForm();
form.parse(req, async function (err, fields, files) {
await saveFile(files.file);
return res.status(201).send("");
});
};
const saveFile = async (file) => {
const data = fs.readFileSync(file.path);
fs.writeFileSync(`./public/${file.name}`, data);
await fs.unlinkSync(file.path);
return;
};
Generally speaking,in your api file,you should disable the default bodyParser,and write your own parser

Resources