I am trying to fetch data from my mongoDB with NextJS getServerSideProps. Adding data is working but when I use find it does not work.
Here is my model
import { Schema, model, models } from "mongoose";
const testSchema = new Schema({
name: String,
email: {
type: String,
required: true,
unique: true,
}
});
const Test = models.Test || model('Test', testSchema);
console.log(models);
export default Test;
This works so my connection is okay
const createTest = async (event) => {
event.preventDefault();
const name = event.target.name.value;
const email = event.target.email.value;
const res = await fetch('/api/test/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name,
email,
}),
});
const data = await res.json();
console.log('data: ', data);
};
But this does not work. It shows the 404 page even though there is data
export const getServerSideProps = async () => {
try {
await connectDB();
const tests = await Test.find();
console.log('FETCHED DOCUMENT');
return {
props: {
tests: JSON.parse(json.stringify(tests)),
}
}
} catch (error) {
console.log(JSON.stringify(error,null,2));
return {
notFound: true
}
}
}
Thank you in advance!
Related
It mess me up... I'm frustrated.
How to use the 'validToken' variable to add it to the auth line for headers? It catches the error message (fetchHeaders func)...
I can't understand why the 'axios' authentification doesn't work for auth request (returns 'headers fetched with error!'), but works if I set validToken hardcoded..
It returns me validToken correctly for template...
Pls help!
Thx in advance!
#App.vue
<script>
import axios from 'axios';
const FormData = require('form-data');
const API_URL = "https://my_api_path.com/";
let data = new FormData();
data.append('username', 'my_username');
data.append('password', 'my_password');
let config = {
method: 'post',
url: `${API_URL}/login`,
data: data
}
let validToken = ""
export default {
data() {
return {
validToken: "",
headers: []
}
},
methods: {
async userLogin() {
try {
await axios(config)
.then((resp) => {
this.validToken = resp.data.access_token;
});
Token = this.validToken;
} catch(err) {
console.log(err)
}
},
async fetchHeaders() {
try {
let config = {
headers: {
Authorization: `Bearer ${validToken}`
}
}
const resp = await axios.get(`${API_URL}/headers/`,
config
)
this.headers = resp.data;
} catch (err) {
console.error("headers fetched with error!");
}
}
},
mounted() {
this.userLogin(),
this.fetchHeaders()
}
}
</script>
Fixed according the #EstusFlask recommendation.
'userLogin' func moved to mounted:
async mounted() {
await axios(config)
.then((resp) => {
validToken = resp.data.access_token
});
}
This is my server file.
In context I am not getting the request while my test is getting pass while test the required scenario.
export async function buildTestServer({
user,
headers,
roles,
}: {
user?: User;
headers?: { [key: string]: string };
roles?: Role;
}) {
const schema = await tq.buildSchema({
authChecker: AuthChecker,
validate: false,
resolvers: allResolvers(),
scalarsMap: [{ type: GraphQLScalarType, scalar: DateTimeResolver }],
});
const server = new ApolloServer({
schema,
context: async ({ req }) => {
const authHeader = headers?.authorization;
if (authHeader) {
const token = extractTokenFromAuthenticationHeader(authHeader);
try {
const user = await new UserPermissionsService(token).call();
return { req, user };
} catch {
return { req };
}
} else {
if (user) {
let capabilities: any = [];
if (roles) {
capabilities = roles.capabilities;
}
return {
req,
user: {
id: user.id,
customerId: user.customerId,
capabilities,
},
};
} else {
return { req };
}
}
},
});
return server;
}
And this is my test file from where I am sending the request to the server.
My test is getting passed but I am not getting the request headers. I want to check the the request. Can anybody help me out ?
const GET_LIST = `
query GetList($listId: String!) {
GetList(listId: $listId) {
id
}
}
`;
test('Get Lists', async () => {
const customer = await CustomerFactory.create();
const user = await UserFactory.create({ customerId: customer.id });
const list = await ListFactory.create({
customerId: customer.id,
});
const server = await buildTestServer({ user });
const result = await server.executeOperation({
query: GET_LIST,
variables: {
listId: list.id
},
});
var length = Object.keys(result.data?.GetList).length;
expect(length).toBeGreaterThan(0);
});
I can successfully view my GraphQL query via apollo-graphql-studio: The resolver is correctly configured, but I'm not sure how to render data.
I know that the next-js swr react-hook is performant, so I'd like to fetch data via the swr method:
import useSWR from "swr";
const Query = `
books {
title
}
`;
export default function Home() {
const fetcher = async () => {
const response = await fetch("/api/graphql", {
body: JSON.stringify({ query: Query }),
headers: { "Content-type": "application/json" },
method: "POST"
});
const { data } = await response.json();
return data;
};
const { data, error } = useSWR([Query], fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return (
<div>
<div>hello {data?.books?.title}</div>
</div>
);
}
This is just returning loading... so the data is clearly not correctly fetched. Although, as I said, I can retrieve it via the Apollo-graphql-studio IDE.
The console error is a 400 Bad Request on the API route: /api/graphql, so this is where the problem is.
How can I render the data?
Here's the GraphQL API:
import Cors from 'micro-cors'
import { gql, ApolloServer } from 'apollo-server-micro'
import { Client, Map, Paginate, Documents, Collection, Lambda, Get } from 'faunadb'
const client = new Client({
secret: process.env.FAUNA_SECRET,
domain: "db.fauna.com",
})
export const config = {
api: {
bodyParser: false
}
}
const typeDefs = gql`
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`
const resolvers = {
Query: {
books: async () => {
const response = await client.query(
Map(
Paginate(Documents(Collection('Book'))),
Lambda((x) => Get(x))
)
)
const books = response.data.map(item => item.data)
return [...books]
},
},
}
const cors = Cors()
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
},
introspection: true,
playground: true,
})
const serversStart = apolloServer.start()
export default cors(async (req, res) => {
if (req.method === "OPTIONS") {
res.end();
return false;
}
await serversStart;
await apolloServer.createHandler({ path: '/api/graphql' })(req, res)
})
I have the following code but it renders the cookieData undefined on the first render and query, so the query doesn't get the cookie and it fails authetication. Any way to make the query wait for the call to the cookie api to come back before running.
const { data: cookieData, error: cookieError } = useSWR(
"/api/cookie",
fetcher
);
console.log(cookieData);
var test = `Bearer ${cookieData}`;
const { loading, error, data } = useQuery(FORMS, {
context: {
headers: {
authorization: test,
},
},
});
UPDATE: I ended up using lazy query for the above, but I will try skip as well, but I have been trying to implement skip on this mutation now and it says the id is undefined, it consoles on the page but is undfined first a few times.
const addFormClicked = async (data) => {
//console.log(data);
const res = await createForm({
variables: {
name: data.name,
user: user.id,
},
skip: !user.id,
});
console.log(res);
Router.push(`/formBuild/${res.data.createForm._id}`);
};
Here's the whole code for context
import { useMutation, gql } from "#apollo/client";
import Layout from "../components/Layout";
import { useForm } from "react-hook-form";
import { useRouter } from "next/router";
import { FORMS } from "../components/Layout";
import useSWR from "swr";
import { useState } from "react";
const ADD_FORM = gql`
mutation AddForm($name: String!, $id: ID!) {
createForm(data: { name: $name, user: { connect: $id } }) {
name
_id
}
}
`;
const fetcher = (url) => fetch(url).then((r) => r.json());
export default function AddForm() {
const { data: user } = useSWR("/api/user"); // add
const { data: cookieData, error: cookieError } = useSWR(
"/api/cookie",
fetcher
);
var test = `Bearer ${cookieData}`;
const Router = useRouter();
const [
createForm,
{
data: createFormData,
error: createFormError,
loading: createFormLoading,
},
] = useMutation(ADD_FORM, {
refetchQueries: [{ query: FORMS }],
context: {
headers: {
authorization: test,
},
},
});
const addFormClicked = async (data) => {
//console.log(data);
const res = await createForm({
variables: {
name: data.name,
user: user.id,
},
skip: !user.id,
});
console.log(res);
Router.push(`/formBuild/${res.data.createForm._id}`);
};
const { register, handleSubmit, errors, reset } = useForm();
if (createFormLoading) return <p>Loading</p>;
if (createFormError) return <p>Error: {createFormError.message}</p>;
//console.log(createFormData);
return (
<Layout>
<form onSubmit={handleSubmit(addFormClicked)}>
<h1>Form Name</h1>
<input type="text" name="name" ref={register()} />
<button type="submit">Add Form</button>
</form>
</Layout>
);
}
UPDATE: The user needed to be id, seen below
const addFormClicked = async (data) => {
//console.log(data);
const res = await createForm({
variables: {
name: data.name,
id: user.id, //NOT user:user.id BUT id:user.id
},
skip: !user.id,
});
console.log(res);
Router.push(`/formBuild/${res.data.createForm._id}`);
};
The user variable will be undefined while the query is in a loading state. Same with cookieData. There's no skip option available in useMutation since it does not automatically execute the mutation when the component renders.
A simple solution would be to render the form if only if user and cookieData exist. This way, you can know for sure the user id and token will be available when the form gets submitted.
// Add `userError` to use in combination with `user` to check if the query is loading
const { data: user, error: userError } = useSWR('/api/user', userFetcher)
const [
createForm,
{ data: createFormData, error: createFormError, loading: createFormLoading },
] = useMutation(ADD_FORM, {
refetchQueries: [{ query: FORMS }],
})
const addFormClicked = async (data) => {
const res = await createForm({
context: {
headers: {
authorization: `Bearer ${cookieData}`,
},
},
variables: {
name: data.name,
user: user.id,
},
})
Router.push(`/formBuild/${res.data.createForm._id}`)
}
if (userError || cookieError) {
return <div>Something went wrong</div>
}
if (!user || !cookieData) {
return <div>Loading...</div>
}
// Render form
I'm trying to attach images with regular text inputs into my form in order to submit to my MongoDB.
This is what my function to create a post looks like:
const [postData, setPostData] = useState({
text: '',
images: null,
postedto: auth && auth.user.data._id === userId ? null : userId
});
const { text, images, postedto } = postData;
const handleChange = name => e => {
setPostData({ ...postData, [name]: e.target.value, images: e.target.files });
};
const createPost = async e => {
e.preventDefault();
await addPost(postData, setUploadPercentage);
};
From there I move into my action addPost; on this function I call two API routes:
// #route POST api/v1/posts
// #description Add post
// #access Private
// #task DONE
export const addPost = (formData, setUploadPercentage) => async dispatch => {
try {
// ATTACH FILES
let fileKeys = [];
for(let file of formData.images) {
const uploadConfig = await axios.get(`${API}/api/v1/uploads/getS3url?type=${file.type}`);
await axios.put(uploadConfig.data.url, file, {
headers: {
'Content-Type': file.type
}
});
fileKeys.push(uploadConfig.data.key);
}
console.log(fileKeys);
// INSERT NEW BLOG
const config = {
headers: {
'Content-Type': 'multipart/form-data; application/json'
},
onUploadProgress: ProgressEvent => {
setUploadPercentage(
parseInt(Math.round(ProgressEvent.loaded * 100) / ProgressEvent.total)
);
// Clear percentage
setTimeout(() => setUploadPercentage(0), 10000);
}
};
formData.images = fileKeys;
const res = await axios.post(`${API}/api/v1/posts`, formData, config);
dispatch({
type: ADD_POST,
payload: res.data
});
dispatch(setAlert('Post Created', 'success'));
} catch (err) {
const errors = err.response && err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: POST_ERROR,
payload: { msg: err.response && err.response.statusText, status: err.response && err.response.status }
});
}
};
My getS3url function looks exactly like this:
exports.uploadFile = asyncHandler(async (req, res, next) => {
const { type } = req.query;
const fileExtension = type.substring(type.indexOf('/') + 1);
const key = `${process.env.WEBSITE_NAME}-${req.user._id}-${
req.user.email
}-${Date.now().toString()}.${fileExtension}`;
const params = {
Bucket: process.env.AWS_BUCKET_NAME,
Key: key,
ContentType: type
};
s3.getSignedUrl(`putObject`, params, (err, url) => {
if (err) {
return next(
new ErrorResponse(
`There was an error with the files being uploaded`,
500
)
);
}
return res.status(201).json({ success: true, key: url });
});
});
I would like to point out that every post might have more than one image file and the function should return a signedURL for each single file; let's say I upload two files, I then should have two URLS retrieved in order to attach them into my post.
I'm sure there's nothing wrong with the way I;m managing state to submit data because it always return what I expect when using on console.log(postData) , even the files are shown.
Now I'm assuming the problem resides on my action, especially the code before the /// INSERT NEW BLOG comment because when I console.log(fileKeys) nothing is returned, not even an error/undefined/null.....I mean just nothing!.
My uploadFile is working fine when used with a single file....well not really because yes, it returns an URL of the 'supposed' uploaded file but when I get into my AWS console/bucket, there's nothing..but thats for its own post.
What I need help with?
Well, I'm trying to upload one/multiple files into my AWS using signedURL to return them as strings and attach them into my post. Is there any problem with my action file?.
Thanks!!.
for my case, I have been looping through the images and generating signed URLs and returning them since s3 doesn't support the signed URL option for multiple files at once.
In the end I found my own solution, here it is:
export const addPost = (formData, images, setUploadPercentage) => async dispatch => {
try {
let fileKeys = [];
for(let i = 0; i < images.length; i++) {
/// STEP 3
const token = localStorage.getItem("xAuthToken");
api.defaults.headers.common["Authorization"] = `Bearer ${token}`
const uploadConfig = await api.get(`/uploads/getS3url?name=${images[i].name}&type=${images[i].type}&size=${images[i].size}`);
// STEP 1
delete api.defaults.headers.common['Authorization'];
await api.put(uploadConfig.data.postURL, images[i], {
headers: {
'Content-Type': images[i].type
}
});
fileKeys.push(uploadConfig.data.getURL);
}
// INSERT NEW BLOG
const config = {
onUploadProgress: ProgressEvent => {
setUploadPercentage(
parseInt(Math.round(ProgressEvent.loaded * 100) / ProgressEvent.total)
);
setTimeout(() => setUploadPercentage(0), 10000);
}
};
// STEP 2
const token = localStorage.getItem("xAuthToken");
api.defaults.headers.common["Authorization"] = `Bearer ${token}`
const res = await api.post(`/posts`, {...formData, images: fileKeys}, config);
dispatch({
type: ADD_POST,
payload: res.data
});
dispatch(setAlert('Post Created', 'success'));
} catch (err) {
const errors = err.response && err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: POST_ERROR,
payload: { msg: err.response && err.response.statusText, status: err.response && err.response.status }
});
}
};