Sveltekit typeError during post request with fetch - fetch

I got a nestjs application. It is listening on localhost:3000. I have health check, i can ping with curl or insomnia and it is working correctly. I can use localhost/3000/api/register to register a new user without any problem. I wanted to try it with sveltekit. And i had an issue when i tried to fetch data it and i got an error:
TypeError: fetch failed
at fetch (/Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/undici/index.js:105:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async send (/Users/marcelljuhasz/Development/svelte-kit-demo/src/lib/api.ts:16:13)
at async default (/Users/marcelljuhasz/Development/svelte-kit-demo/src/routes/register/+page.server.ts:23:15)
at async handle_action_json_request (file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/runtime/server/page/actions.js:51:16)
at async resolve (file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/runtime/server/index.js:356:17)
at async respond (file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/runtime/server/index.js:229:20)
at async file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/exports/vite/dev/index.js:444:22
I checked my server i got the cors enabled. The front end is listening to: localhost:5173.
I have this code inside:
app.enableCors({
origin: 'http://localhost:5173',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
allowedHeaders: 'Content-Type, Accept',
preflightContinue: true,
});
I am learning sveltekit now and i checked a project inside the github repository to see how it is works but i read the documentations too:
https://github.com/sveltejs/realworld
The structure almost the same i have the lib folder with the api.ts
import { error } from '#sveltejs/kit';
const base = 'http://localhost:3000/api';
async function send({ method, path, data }) {
const opts = { method, headers: {} };
if (data) {
opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(data);
}
const res = await fetch(`${base}/${path}`, opts);
if (res.ok || res.status === 422) {
const text = await res.text();
return text ? JSON.parse(text) : {};
}
console.log(res);
throw error(res.status);
}
export function get(path) {
return send({ method: 'GET', path });
}
export function del(path) {
return send({ method: 'DELETE', path,});
}
export function post(path, data) {
return send({ method: 'POST', path, data });
}
export function put(path, data) {
return send({ method: 'PUT', path, data });
}
I have a register.svelte in the routes dir. With +page.svelte and +page.server.ts is the same like in the repository, i just exclude my own fields. The data input is return in the correct format.
+page.server.ts looks like this, almost the same like in the repo:
import { fail, redirect } from '#sveltejs/kit';
import * as api from '$lib/api.js';
/** #type {import('./$types').PageServerLoad} */
export async function load({ parent }) {
const { user } = await parent();
if (user) throw redirect(307, '/');
}
/** #type {import('./$types').Actions} */
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const user = {
username: data.get('username'),
email: data.get('email'),
password: data.get('password')
};
const body = await api.post('register', { ...user });
if (body.errors) {
return fail(401, body);
}
console.log(body)
throw redirect(307, '/');
}
};
So in a nutshell i got typerror after i hit the sign uo button. On my server log tells nothing. I see this log in the sveltekit log. I tried to check cors, but it is okey and i haven't got any cors errors in dev console. I checked in my console with curl to check if is the server available. I tried to post, get with insomnia and curl. And it is worked as expected. I have no clue for this. It is wierd if i check the chrome dev tool the request. In the general tab the request url is: localhost:5173 which is the default vite.config for sveltekit server. But i passing my own server which is localhost:3000 and i dont understand what is this behavor. If anybody have experience with sveltekit i am curious what is wrong. I tried to fetch data with an own svelte file without +page.server.ts, i put this fetch method into the component file and it is worked. Wierd.

Related

How to handle next api route using dynamic id

I am using NextJS and created three layers that separate the logic.
The purpose is to minimise the error handling to the getServerSideProps section. I want to get all the lists from the database.
In the first layer, in the API route I created a folder called get-all-lists and a file [userId].js. The get request will be 'http://localhost:3000/api/get-all-lists/iudga937gr8'. Bellow there is the api route that get all the lists with the help of Prsima. It is working perfectly
import prisma from '../../../lib/prisma'
export default async function handler(req, res) {
const { userId } = req.query;
if (req.method === 'GET') {
try {
const shoppingLists = await prisma.List.findMany({ where: { userId: userId }});
res.status(200).json({lists: shoppingLists});
}
catch (error) {
console.log(error);
res.status(500).json({ message: 'Something went wrong. Please try again'});
}
}
else {
res.status(500).json({message: 'Invalid method requested!'});
}
}
The next layer, is the abstraction one which sent the final result to getServerSideProps. I created this because I need to fetch alot of requests and it would be too messy...
export const getAllLists = async userId => {
try {
const lists = await axios.get(`/api/get-all-lists/${userId}`);
return lists;
}
catch (error) {
console.log('Abstraction layer error: ', error);
return 'Something went wrong. Please try again later';
}
}
The problem arise here. In the postman I have the right result. In postman I use http://localhost:3000/api/get-all-lists/clbcpc0hi0002sb1wsiea3q5d and the server sent me the array specified.
But this function does not work and send me this error:
Abstraction layer error: TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:371:5)
at onParseError (node:internal/url:552:9)
at new URL (node:internal/url:628:5)
at dispatchHttpRequest (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/adapters/http.js:176:20)
at new Promise (<anonymous>)
at http (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/adapters/http.js:112:10)
at Axios.dispatchRequest (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/dispatchRequest.js:51:10)
at Axios.request (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/Axios.js:142:33)
at Axios.<computed> [as get] (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/Axios.js:168:17)
at Function.wrap [as get] (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/helpers/bind.js:5:15) {
input: '/api/get-all-lists/clbcpc0hi0002sb1wsiea3q5d',
code: 'ERR_INVALID_URL'
}
I also tried to paste the localhost in the browser and it have no problem.
You could extract the functionality into /lib/getAllList.js:
import prisma from './prisma';
export default async function getAllLists(userId) {
return await prisma.List.findMany({ where: { userId: userId }});
}
Then use it in your API route:
import getAllLists from '../../../lib/getAllLists';
export default async function handler(req, res) {
const { userId } = req.query;
if (req.method === 'GET') {
try {
const shoppingLists = await getAllLists(userId);
res.status(200).json({lists: shoppingLists});
}
catch (error) {
console.log(error);
res.status(500).json({ message: 'Something went wrong. Please try again'});
}
}
else {
res.status(500).json({message: 'Invalid method requested!'});
}
}
Then use it in getServerSideProps:
import getAllLists from 'path/to/lib/getAllLists';
export async function getServerSideProps(context) {
const { userId } = context.params;
const shoppingLists = await getAllLists(userId);
return {
props: {
shoppingLists,
},
};
}

How do I use API middlewares to protect API routes from unauthenticated users in Next.js?

I have a next.js app that has several API routes that I am hoping to protect from users who are not logged in. Using next-auth, I understand that I can add the following code to each API route to achieve this.
import { getSession } from 'next-auth/client'
export default async (req, res) => {
const session = await getSession({ req })
if (session) {
res.send({ content: 'This is protected content. You can access this content because you are signed in.' })
} else {
res.send({ error: 'You must be sign in to view the protected content on this page.' })
}
}
However, I was wondering if it is possible to use API middlewares, so I am not repeating the same code over and over again? I read through the Next.js API middlewares documentation (https://nextjs.org/docs/api-routes/api-middlewares) and did the following:
import Cors from 'cors';
import { getSession } from 'next-auth/react';
function initMiddleware(middleware) {
return (req, res) =>
new Promise((resolve, reject) => {
middleware(req, res, async (result) => {
const session = await getSession({ req });
if (!session) {
return reject(result);
}
return resolve(result);
});
});
}
const cors = initMiddleware(
Cors({
methods: ['GET', 'POST', 'OPTIONS'],
})
);
export default async function handler(req, res) {
await cors(req, res);
\* fetching from database *\
Although it works, the following error is returned when I tried to access the API route when unauthenticated, and it feels like I'm not doing it properly.
error - null
wait - compiling /_error (client and server)...
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:561:11)
at DevServer.renderError (/Users/alextung/Desktop/Projects/askit/node_modules/next/dist/server/next-server.js:1677:17)
at DevServer.run (/Users/alextung/Desktop/Projects/askit/node_modules/next/dist/server/dev/next-dev-server.js:452:35)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async DevServer.handleRequest (/Users/alextung/Desktop/Projects/askit/node_modules/next/dist/server/next-server.js:325:20) {
code: 'ERR_HTTP_HEADERS_SENT'
}
error - Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Would really appreciate some help on this given that this is my first time working with middlewares. Thank you!

Page with getInitialProps and getStaticProps

I have a page where I need to fetch data with axios which contains credentials and depending on the parameter in the url make another request using the data from the previous request.
I don't know how I can approach this since I can use both getInitialProps and getStaticProps in pages, and in components I can't use them at all.
The code below works, but I don't know how to break it down so that the login is done on the servar side so that I can still get parameters from the URL.
function Result({surveyId, responseId, sessionKey}) {
return (
<>
<div>surveyId: {surveyId}</div>
<div>responseId: {responseId}</div>
<div>sessionKey: {sessionKey}</div>
</>
)
}
Result.getInitialProps = async ({ query }) => {
// Example of URL
// http://localhost:3000/result?surveyId=12345&responseId=6
const surveyId = query.surveyId
const responseId = query.responseId
const data = { method: 'get_session_key', params: ['admin', 'password'], id: 1 }
const options = {
headers: {
'connection': 'keep-alive',
'content-type': 'application/json'
}
}
const sessionKey = await axios.post(url, data, options).then(
(response) => {
return response.data.result
},
(error) => {
console.log(error)
}
)
return {
surveyId: surveyId,
responseId: responseId,
sessionKey: sessionKey,
}
}
getStaticProps, getInitialProps or getServerSideProps, they all executed only in the pages. Because client makes the request to the pages, so behind the scene, the way how next.js sets up the routing, whenever a request hits a route, next.js first checks if this page has any of those function, and if it has it runs those functions first, gets the results as props and then runs the component functions and passes the props to the component.
getsStaticProps is used for static file generation. A good example is generating of blogs. When you run npm run build,next js will run all api calls and populate the page with blogs. So actually, if you check the build folder, inside html files of pages that getStaticPath is executed, all the data will be already inside that html file. Data will be cached by the server so when user makes a request to those statically generated files, data will be served right away. So you should not run the login process in the getStaticProps since login is a dynamic process.
All those functions are used for prerendering for better SEO optimization. But if you want to load user-specific data you dont need to prerender user-specific data for seo purpose. You could just do client-side as well.
Or you could use next.js api functions, you would be writing the function inside api directory, and from getServerSideProps you would send a request to the api route. That way, if you need to run same code in a different page, instead of writing the same code for authentication, you would be making request the api function and it would handle for you.
I found another solution which uses getStaticPaths and dynamic routes. In my case /pages/survey/[...params].js.
export default function Result({ surveyId, responseId, sessionKey }) {
return (
<>
<div>surveyId: {surveyId}</div>
<div>responseId: {responseId}</div>
<div>sessionKey: {sessionKey}</div>
</>
)
}
export function getStaticPaths() {
return { paths: [], fallback: true }
}
export async function getStaticProps({ params }) {
const surveyId = params.survey[0]
const responseId = params.survey[1]
const data = { method: 'get_session_key', params: ['admin', 'password'], id: 1 }
const options = {
headers: {
'connection': 'keep-alive',
'content-type': 'application/json'
}
}
const url = 'https://example.com'
const sessionKey = await axios.post(url, data, options).then(
(response) => {
return response.data.result
},
(error) => {
console.log(error)
}
)
return {
props: { surveyId, responseId, sessionKey },
revalidate: false,
}
}

excel4node fetch request with next.js api routes not triggering download

I am generating an excel file and want it to be downloaded for the client by triggering an API route using the Next.js framework. I am having trouble triggering the download by using fetch. The download can be triggered by window.open(//urlhere, '_self') but the API call using fetch gives this response on request:
API resolved without sending a response for /api/download?Students= this may result in stalled requests.
The excel4node documentation says we can send an excel document through an API like this:
// sends Excel file to web client requesting the / route
// server will respond with 500 error if excel workbook cannot be generated
var express = require('express');
var app = express();
app.get('/', function(req, res) {
wb.write('ExcelFile.xlsx', res);
});
app.listen(3000, function() {
console.log('Example app listening on port 3000!');
});
Here is my backend download.js which lives in pages/api/:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import Link from "next/link";
import getPartners from "../../components/main";
import excel from "excel4node";
export default function handler(req, res) {
const students = req.query.Students.split(",");
const partners = JSON.stringify(getPartners(students));
let workbook = createExcelList(partners);
workbook.write("PartnerList.xlsx", res);
}
const createExcelList = (partnersJSON) => {
const workbook = new excel.Workbook();
const partnersObject = JSON.parse(partnersJSON);
/* Using excel4node a workbook is generated formatted the way I want */
return workbook;
};
export const config = {
api: {
bodyParser: true,
},
};
And here is the function that is triggered on a button press in the front end.
const makePartners = async () => {
let queryStudents = studentList.join(",");
const url = "http://localhost:3000/api/download?Students=" + queryStudents;
if (studentList.length !== 0) {
try {
const res = await fetch(url, {
headers: {
"Content-Type":
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
},
});
console.log(res);
} catch (e) {
console.log(e);
}
}
};
Which does not trigger the download. But using window.open(url, '_self) does. So, I can trigger the download by changing the function to the following. However I don't think this is the correct way of doing things and would like to be able to understand how to use fetch correctly.
const makePartners = () => {
let queryStudents = studentList.join(",");
const url = "http://localhost:3000/api/download?Students=" + queryStudents;
if (studentList.length !== 0) {
window.open(url, "_Self");
}
};
I am not sure if this is a Next.js issue or not. Does anyone have any insight? Any help would be appreciated.

How to listen for specific Firestore document creation event?

I am implementing a command/response pattern where the user writes to a command collection by calling add with a payload under his own userId, and then gets the data from a similar response path.
However the code below doesn't work, because onSnapshot can not listen for a document that hasn't yet been created (document command.id in the /responses/{userId}/register collection). This would be easy to solve with an onCreate handler, which exists for cloud functions but not for the JS firebase client API it seems.
This is using redux-firestore and some of my app helper functions, but you'll get the idea. The command and response document structures use { payload, error} similar to FSA
Cloud Function
export const register = functions.firestore
.document("commands/{userId}/register/{commandId}")
.onCreate(async event => {
const payload = event.data.get("payload");
const { userId, commandId } = event.params;
const response = db.document(`responses/${userId}/register/${commandId}`)
// possibly something bad will happen
try {
// do something with payload...
return response.set({
payload: "ok" // or pass relevant response data
})
} catch(err) {
return response.set({
error: true
payload: error
})
}
});
Client
export async function register(fs: any, userId: string) {
try {
// issue a new command
const command = await fs.add(
{ collection: `commands/${userId}/register` },
{ payload: fs.firestore.FieldValue.serverTimestamp() }
);
// wait for the response to be created
fs.onSnapshot(
{ collection: `responses/${userId}/register`, doc: command.id },
function onNext(doc) {
const {error, payload} = doc.data()
if (error) {
return notify.error({ title: 'Failed to register', message: payload.message });
}
notify.json(payload);
},
function onError(err) {
notify.error(err);
}
);
} catch (err) {
notify.error(err);
}
}
Is there no such thing as onCreate for web clients?
The only scalable solution I can think of is to store the response data as a child in the command document, but I think it is not as nice, because I suspect you can not make the permissions as strict then.
I would like the user only to be able to write to the command, and only read from the response paths. If I place the response as a child of command, this would not be possible I think?
I'm wondering if I'm not overlooking some API...

Resources