I am currently struggling with Nuxt's Axios module: https://axios.nuxtjs.org/.
I would like to get some data from a specific endpoint where I have to use Basic Authentication.
Normally, with Axios, I would do something like:
await axios.get(
'http://endpoint',
{},
{
withCredentials: true,
auth: {
username: 'userame',
password: 'pw'
}
}
)
Unfortunately, with Nuxt's Axios module, it seems it is not that easy...
I tried something like:
const data = await this.$axios.$get(
'http://endpoint',
{},
{
credentials: true,
auth: {
username: 'user',
password: 'pw'
}
}
)
But that leaves me with a 401 Unauthorized...
What am I missing here?
The second argument to axios.get() (and $axios.$get()) is the Axios config, but you've passed it as the third argument (which is effectively ignored). The API likely omits the data argument from axios.get() because data doesn't apply in this context.
The solution is to replace the 2nd argument with your config:
const data = await this.$axios.$get(
'http://endpoint',
// {}, // <-- Remove this! 2nd argument is for config
{
credentials: true,
auth: {
username: 'user',
password: 'pw'
}
}
)
Related
Here is the code for my server which works fine. I am trying to achieve this with netlify's serverless functions which I have pasted further below.
CODE ON STANDARD SERVER-HEROKU
const ratingController = {};
const Rating = require("../models/ratingModel");
ratingController.getAllRatings = async function (req, res) {
const rating = await Rating.find();
res.status(200).json({
status: "success",
data: rating,
});
};
ratingController.createOneRating = async function (req, res) {
console.log(req.body);
req.body.userIp = req.headers["x-forwarded-for"];
const rating = await Rating.create(req.body);
// const rating = new Rating(req.body);
// await rating.save();
res.status(200).json({
status: "success",
data: {
rating,
},
});
};
PART 1 - GET REQUEST
Here's my code for the getAllRatings and it works fine
SERVERLESS FUNCTION - NETLIFY
const { MongoClient } = require("mongodb");
require("dotenv").config();
exports.handler = async function getData(event, context) {
const client = await MongoClient.connect(process.env.DB, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
const db = client.db();
try {
const slug = event.queryStringParameters.id;
const data = await db.collection("collectionName").find({ slug }).toArray();
client.close();
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
status: "success",
data: data,
}),
};
} catch (error) {
console.log(error);
return {
statusCode: 400,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
status: "fail",
message: error.message,
}),
};
}
};
My first question for the above is
Just because it works may not mean it's the right way to do it.. I had a few concerns if calling the database each time there's a call is correct and then placing the code the way I have, if it's the way it should be. It's all based on testing and random research. There's no real method being followed so would appreciate some guidance on a more efficient method to do this.
Normally on a regular server the database connection is done just once.. and here I seem to be doing it every time and I am a bit confused if that's ok or not..
PART 2 - POST REQUEST
Here's my code for the POST request createOneRating
SERVERLESS FUNCTION - NETLIFY
const { MongoClient } = require("mongodb");
require("dotenv").config();
exports.handler = async function createRating(event, context) {
const client = await MongoClient.connect(process.env.DB, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
const db = client.db();
try {
console.log(event);
const rating = await db.collection("ratings").insertOne(event.body);
client.close();
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
status: "success",
data: rating,
}),
};
} catch (error) {
console.log(error);
return {
statusCode: 400,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
status: "fail",
message: error.message,
}),
};
}
};
This one does not work as it says
{
"status": "fail",
"message": "Cannot create property '_id' on string ''"
}
And I guess that's because event.body is not where the data is.. but I am not sure how to get the POST data in a serverless deployment.
So my second question is
How do I retrieve the data sent by a POST request. As there's no request parameter I am a bit confused.
Also I'd like to add the IP of the user so other than the POST data I'd also need some help on how to do this
req.body.userIp = req.headers["x-forwarded-for"];
Based on my own research, I have answers to the questions and am placing them here for my own reference and for those who might face a similar situation in the future.
Question 1 : Is it ok to make a database connection on every call that's made to a serverless function
It seems it's ok to do this and for those, like me, who thought that maybe it was not the right way to do it, it's definitely not the wrong way. Maybe there's an efficient way to do this and I'd be open to learn more about this if possible. But for now, it's good to know that it's not wrong to connect to the database each time you make a call.
Question 2: How to make a POST request on a serverless as there's no request parameter
I was not aware that the event parameter is in fact a replacement for the request parameter and that the headers and body are properties of the event object and can be accessed in the same way ie event.body and event.headers. Here's a link that could save you some time to confirm this.
(https://docs.netlify.com/functions/build-with-javascript/#synchronous-function-format)
And if you, like me, don't know if a serverless function can be defined as GET or POST or run into an issue where the POST request gets converted into a GET when making a function call here's a link that would help.
How to define Netlify function endpoint as POST?
I have:
rules={{
required: 'This is required.',
validate: (value) => daoExists(value),
}}
with daoExists, I need to check GraphQL endpoint and if it returns true, validation succeeds, otherwise some error message. I am using useQuery hooks from Apollo client for such queries, but now I have a problem, because daoExists can't be a hook. So, what I am doing is I create a daoExists as normal function. Something like this:
export const daoExists = async (name: string): Promise<ValidateResult> => {
const query = `
query Daos($name: String) {
daos(where: {name: $name}){
id
}
}
`;
const data = await fetch(subgraphUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
query,
variables: { name: name },
}),
});
const daos = await data.json();
return daos?.data?.daos?.length > 0 || 'Dao with this name already exists';
};
Which I really don't like to use fetch and I want to continue using useQuery hooks, but I can't. I even tried creating useCallback, but then I can't use useQuery in my useCallback.
Are there any workarounds?
I have the following method from my vuex action block where I send user email and password to backend server and get userId and token on success.
async signIn ({ commit }, formData) {
const graphqlQuery = {
query: `
query{
loginData(
email: "${formData.email}",
password: "${formData.password}"
) {userId, token}
}
`
}
const res = await this.$axios.post('http://localhost:8080/graphql', graphqlQuery, {
headers: {
'Content-Type': 'application/json'
}
})
console.log(res.data) //works
console.log(res.data.loginData.token) //doesn't work
commit('loadUserData', true, res.data.loginData.token, res.data.loginData.userId)
return res.status
}
I can confirm that I'm getting the response data with console.log(res.data). The response data I get is:
But when I console.log(res.data.loginData) I get undefined. And blank on console.log(res.data.loginData.token) and console.log(res.data.loginData.token).
How do I extract the required data token and userId ?
I'm using webstorm as my IDE and it shows loginData as method refering to my backend graphql resolver method.
My frontend is made on Nuxt js, backend on node js with graphql.
Does anyone have any idea to overcome this issue ?
I am trying to write a basic graphql query with fetch that works when using apollo client. But it does not work with node-fetch.
The type definitions look like this:
type Query {
findLeadStat(print: PrintInput!): LeadStatWithPrint
}
input PrintInput {
printa: String!
service: String
}
type LeadStatWithPrint {
answered: Int!
printa: String!
service: String
}
This is the node-fetch query:
const fetch = require('node-fetch');
( async () => {
const uri = `http://localhost:3000/graphql/v1`;
const query = `
query findLeadStat(print: PrintInput!) {
findLeadStat(print: $print){
answered
printa
service
}
}
`;
// I also tried add a query: key inside data object
const data = {
print: {
printa: "62f69234a7901e3659bf67ea2f1a758d",
service: "abc"
}
}
const response = await fetch(uri, {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({query, data})
});
console.log('and the resp: ', response);
})()
It gives me:
url: 'http://localhost:3000/graphql/v1',
status: 400,
statusText: 'Bad Request',
It works in Apollo GraphQL Client. Why doesn't it work with fetch?
So when I was using async await with node-fetch, the response was pretty much useless. It was just telling me there was a 400 bad request error and then give me this long object of properties, none of them containing the actual error message.
But when I changed the fetch call to this:
const response = await fetch(uri, {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query, variables}) // same as query: query, variables: variables
})
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error('ERROR: ', err));
There two lines right here:
.then(res => res.json())
.then(json => console.log(json))
made it clear what the issue was:
{
errors: [
{
message: 'Syntax Error: Expected $, found Name "fingeprint"',
locations: [Array],
extensions: [Object]
}
]
}
It appears node-fetch has two async events occurring and so await had to be used twice:
const response = await fetch(uri, {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query, variables}) // same as query: query, variables: variables
})
console.log('and the resp: ', await response.json());
A 400 status indicates your query was invalid or malformed. When this happens, the response will include a JSON body with an errors array that can be inspected to determine what exactly went wrong.
In this particular case, the issue is that your query includes a non-nullable variable ($print) but this variable was not provided along with the query.
When making a GraphQL request, the request body should be a JSON object with a query property and two other optional properties -- variables and operationName. operationName is used to identify which operation to execute if multiple operations were included in the provided document (the query property). Any non-nullable variables defined in the executed operation must be included as properties under the variables property, which is also an object. Nullable properties may be omitted altogether.
In other words, you need to change the data property in your request to variables in order for the server to recognize that the variable was provided with the request.
How do I write the Joi schema for a file that I am sending as a response?
My route returns this return h.file(filename, { mode: 'attachment'}).code(201); and well, the content-dispostion response header is attachment; filename=entries.csv.
I can maybe check the object structure of the response that's going out but is there a way Joi provides a property to check for files in the response?
Here's the Github issue you might wanna track
I misunderstood the question - it was about validating response headers, not request ones.
Short answer: it cannot be done.
Long answer:
Based on hapijs 17.5.3 https://hapijs.com/api#-routeoptionsresponseoptions it seemed doable with a function:
server.route({
method: 'GET',
path: '/file',
options: {
handler: (request, h) => {
return h.file('foobar.csv', { mode: 'attachment'}).code(201);
},
response: {
schema: async (value, options) => {
console.log('validating response:', value);
}
}
}
});
But this approach doesn't work.
It's not supported by hapijs, you'll get an exception from line 151: https://github.com/hapijs/hapi/blob/76fcd7fa97747c92501b912d64db459d7172cb26/lib/validation.js
which is:
if (!response.isBoom &&
request.response.variety !== 'plain') {
throw Boom.badImplementation('Cannot validate non-object response');
}
here's how you can validate headers on requests:
'use strict';
const Joi = require('joi');
const ErrorHandler = require('../handlers/errorHandler');
const fileUploadValidator = {
config: {
validate: {
params: {
env: Joi.string().min(2).max(10).required()
},
query: {
id: Joi.number().integer().min(0).required()
},
headers: Joi.object({
'x-request-id': Joi.string().guid().required(),
'content-disposition': Joi.string().regex(/attachment;\s*filename=.+\.csv/gi).insensitive().required()
}).options({ allowUnknown: true }),
failAction: ErrorHandler.apply_genericHandler
}
}
};
module.exports = fileUploadValidator;
Route definition:
server.route({
method: 'POST',
path: '/{env}/v1/fileUpload',
handler: FileUploadHandler.apply,
options: FileUploadValidator.config
});
you may need to tweak it a bit. I've built it based on your question.