My frontend is localhost:3000, and my GraphQL server is localhost:3333.
I've used react-apollo to query/mutate in JSX land, but haven't made a query/mutation from Express yet.
I'd like to make the query/mutation here in my server.js.
server.get('/auth/github/callback', (req, res) => {
// send GraphQL mutation to add new user
});
Below seems like the right direction, but I'm getting TypeError: ApolloClient is not a constructor:
const express = require('express');
const next = require('next');
const ApolloClient = require('apollo-boost');
const gql = require('graphql-tag');
// setup
const client = new ApolloClient({
uri: 'http://localhost:3333/graphql'
});
const app = next({dev});
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
const server = express();
server.get('/auth/github/callback', (req, res) => {
// GraphQL mutation
client.query({
query: gql`
mutation ADD_GITHUB_USER {
signInUpGithub(
email: "email#address.com"
githubAccount: "githubusername"
githubToken: "89qwrui234nf0"
) {
id
email
githubToken
githubAccount
}
}
`,
})
.then(data => console.log(data))
.catch(error => console.error(error));
});
server.listen(3333, err => {
if (err) throw err;
console.log(`Ready on http://localhost:3333`);
});
})
.catch(ex => {
console.error(ex.stack);
process.exit(1);
});
This post mentions Apollo as the solution, but doesn't give an example.
How do I call a GraphQL mutation from Express server :3000 to GraphQL :3333?
This is more likely to be what you're looking for:
const { createApolloFetch } = require('apollo-fetch');
const fetch = createApolloFetch({
uri: 'https://1jzxrj179.lp.gql.zone/graphql',
});
// Example # 01
fetch({
query: '{ posts { title } }',
}).then(res => {
console.log(res.data);
});
// Example # 02
// You can also easily pass variables for dynamic arguments
fetch({
query: `
query PostsForAuthor($id: Int!) {
author(id: $id) {
firstName
posts {
title
votes
}
}
}
`,
variables: { id: 1 },
}).then(res => {
console.log(res.data);
});
Taken from this post, might be helpful to others as well: https://www.apollographql.com/blog/graphql/examples/4-simple-ways-to-call-a-graphql-api/
You can use graphql-request, it is a simple GraphQL client.
const { request } = require('graphql-request');
request('http://localhost:3333/graphql', `mutation ADD_USER($email: String!, $password: String!) {
createUser(email: $email, password: $password) {
id
email
}
}`, {email: 'john.doe#mail.com', password: 'Pa$$w0rd'})
.then(data => console.info(data))
.catch(error => console.error(error));
It also support CORS.
const { GraphQLClient } = require('graphql-request');
const endpoint = 'http://localhost:3333/graphql';
const client = new GraphQLClient(endpoint, {
credentials: 'include',
mode: 'cors'
});
client.request(`mutation ADD_USER($email: String!, $password: String!) {
createUser(email: $email, password: $password) {
id
email
}
}`, {email: 'john.doe#mail.com', password: 'Pa$$w0rd'})
.then(data => console.info(data))
.catch(error => console.error(error));
I use it to make E2E tests.
As you are getting ApolloClient with require instead of import I think you are missing this part:
// es5 or Node.js
const Boost = require('apollo-boost');
const ApolloClient = Boost.DefaultClient;
or
const ApolloBoost = require('apollo-boost');
const ApolloClient = ApolloBoost.default;
Try one of those and see if it works.
I'd like to add one more way to query from express.
This is what I ended up with.
install required packages
npm install graphql graphql-tag isomorphic-fetch
write graphql on separate file (myQuery.js)
const gql = require('graphql-tag');
const query = gql`
query($foo: String) {
// Graphql query
}
}
Main file
const { print } = require('graphql/language/printer');
const query = require('./myQuery');
require('isomorphic-fetch');
// other logic
const foo = "bar"
const token = "abcdef"
await fetch('https://example.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'authorization': `Bearer ${token}`,
},
body: JSON.stringify({
query: `${print(query)}`,
variables: { foo },
}),
})
Related
Context :
I make an API with API-Platform, and I consume this API with Vue 3 and HTTP client Axios
I have two entities in my API :
Author : name(string)
Text : content(string), author(relation to Author)
So a text item is relate to an Author...
Problem :
In Vue 3, I want to call my API for get Text entity.
But in the author column (<td> {{ item.author }} </td>) i have juste the URI reference (/api/authors/2) but I need the name of author...
Solution I tried :
<td> {{ getAuthor(item.author) }} </td>
(authorLink = /api/authors/2)
methods: {
getAuthor: async function (authorLink){
const res = await axios.get('http://127.0.0.1:8000' + authorLink)
console.log(res.data.name)
return res.data.name
}
Result of my solution :
With console.log() : 'JohnDoe' -> this work !
With return : '"[object Promise]"' -> this didnt work..
This way to get the return name with async/await pattern.
And axios needs a Accept-Encoding with correct format.
const getAuthor = async () => {
...
const res = await axios.get(...);
return Promise.resolve(res.data.name);
};
getAuthor()
.then(result => {
console.log(result);
})
Demo code
const axios = require("axios");
const getAuthor = async () => {
try {
const res = await axios.get('https://jsonplaceholder.typicode.com/users/1',
{
headers: {
'Accept-Encoding': 'application/json',
}
}
);
return Promise.resolve(res.data.name);
} catch (error) {
return Promise.reject(error);
}
};
getAuthor()
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error);
});
Result this code
$ node get-data.js
Leanne Graham
This is express server version
const express = require("express")
const axios = require("axios")
const cors = require("cors")
const PORT = 3030
const app = express()
app.use(express.urlencoded({ extended: true }))
app.use(cors())
const getAuthor = async () => {
try {
const res = await axios.get('https://jsonplaceholder.typicode.com/users/1',
{
headers: {
'Accept-Encoding': 'application/json',
}
}
);
return Promise.resolve(res.data.name);
} catch (error) {
return Promise.reject(error);
}
};
app.get("/users/:id", async (req, res) => {
getAuthor()
.then(result => {
res.json(result)
})
.catch(error => {
console.error(error);
});
})
app.listen(PORT, (err) => {
if (err)
console.log("Error in server setup")
console.log("Server listening on Port", PORT);
})
install dependencies
npm install express axios cors
run it
$ node data-server.js
Server listening on Port 3030
Open this URL Chrome and open DevTool by F12
http://localhost:3030/users/1
I am able to get a basic test working with Jest, but when I try to refactor it to use Jest's manual mocks features, the test no longer works.
Any ideas what I could be doing wrong?
Thank you for your time 🙏
error message:
TypeError: _backendService.default.post is not a function
16 |
17 | return $axios
> 18 | .post(`${RESOURCE_PATH}/batch_upload/`, formData, {
| ^
19 | headers: {
20 | "Content-Type": "multipart/form-data",
21 | },
in tests/.../actions.spec.js:
//import $axios from "#/services/backend-service"; // could not get manual mock to work
import actions from "#/store/modules/transactions/actions";
//jest.mock("#/services/backend-service"); // could not get manual mock to work
// this bit of code works
jest.mock("#/services/backend-service", () => {
return {
post: jest.fn().mockResolvedValue(),
};
});
// this bit of code works:end
describe("store/modules/transactions/actions", () => {
it("uploads transactions succeeds", async() => {
const state = {
commit: jest.fn(),
};
await actions.uploadTransactions(
state,
{'file': 'arbitrary filename'}
)
expect(state.commit).toHaveBeenCalledWith('changeUploadStatusToSucceeded');
});
});
in src/.../__mocks__/backend-service.js:
const mock = jest.fn().mockImplementation(() => {
return {
post: jest.fn().mockResolvedValue(),
};
});
export default mock;
in src/.../backend-service.js:
import axios from "axios";
const API_BASE_URL =
`${process.env["VUE_APP_BACKEND_SCHEME"]}` +
`://` +
`${process.env["VUE_APP_BACKEND_HOST"]}` +
`:` +
`${process.env["VUE_APP_BACKEND_PORT"]}` +
`/` +
`${process.env["VUE_APP_BACKEND_PATH_PREFIX"]}`;
const $axios = axios.create({
baseURL: API_BASE_URL,
headers: {
"Content-Type": "application/vnd.api+json",
},
});
export default $axios;
in src/.../actions.js:
import $axios from "#/services/backend-service";
const RESOURCE_NAME = "transaction";
const RESOURCE_PATH = `${RESOURCE_NAME}s`;
export const actions = {
uploadTransactions(state, payload) {
let formData = new FormData();
formData.append("file", payload["file"]);
return $axios
.post(`${RESOURCE_PATH}/batch_upload/`, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
state.commit("changeUploadStatusToSucceeded");
})
.catch(function (error) {
if (error.response) {
state.commit("changeUploadStatusToFailed");
}
});
},
};
export default actions;
I've tried looking at examples from these resources, but nothing worked for me:
mocking Axios Interceptors: Mocking axios with Jest throws error “Cannot read property 'interceptors' of undefined”
overriding mock implmentations:
Mock.mockImplementation() not working
How do I change the mock implementation of a function in a mock module in Jest
How to change mock implementation on a per single test basis [Jestjs]
Jest Mock Documentation: https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn
Jest Manual Mock documentation:
https://jestjs.io/docs/es6-class-mocks
https://jestjs.io/docs/manual-mocks#examples
using 3rd party libraries: https://vhudyma-blog.eu/3-ways-to-mock-axios-in-jest/
simple actions test example: https://lmiller1990.github.io/vue-testing-handbook/vuex-actions.html#creating-the-action
outdated actions test example:
https://vuex.vuejs.org/guide/testing.html#testing-actions
In case it helps others, I ended up just using spies and did not need to use manual mocks.
These were references that helped me figure it out:
https://silvenon.com/blog/mocking-with-jest/functions
https://silvenon.com/blog/mocking-with-jest/modules/
How to mock jest.spyOn for a specific axios call
And here's the example code that ended up working for me:
in tests/.../actions.spec.js:
import $axios from "#/services/backend-service";
import actions from "#/store/modules/transactions/actions";
describe("store/modules/transactions/actions", () => {
let state;
let postSpy;
beforeEach(() => {
state = {
commit: jest.fn(),
};
postSpy = jest.spyOn($axios, 'post')
});
it("uploads transactions succeeds", async() => {
postSpy.mockImplementation(() => {
return Promise.resolve();
});
await actions.uploadTransactions(
state,
{'file': 'arbitrary filename'},
)
expect(state.commit).toHaveBeenCalledWith('changeUploadStatusToSucceeded');
});
it("uploads transactions fails", async() => {
postSpy.mockImplementation(() => {
return Promise.reject({
response: true,
});
});
await actions.uploadTransactions(
state,
{'file': 'arbitrary filename'},
)
expect(state.commit).toHaveBeenCalledWith('changeUploadStatusToFailed');
});
});
in src/.../actions.js:
import $axios from "#/services/backend-service";
const RESOURCE_NAME = "transaction";
const RESOURCE_PATH = `${RESOURCE_NAME}s`;
export const actions = {
uploadTransactions(state, payload) {
let formData = new FormData();
formData.append("account_id", 1); // change to get dynamically when ready
formData.append("file", payload["file"]);
//$axios.interceptors.request.use(function (config) {
// state.commit("changeUploadStatusToUploading");
// return config;
//});
return $axios
.post(`${RESOURCE_PATH}/batch_upload/`, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.log(response);
state.commit("changeUploadStatusToSucceeded");
})
.catch(function (error) {
if (error.response) {
state.commit("changeUploadStatusToFailed");
}
});
},
};
export default actions;
I am trying to build a small website. In that i using React for frontend, Nodejs for backend, and some third party api. Here my idea is, first to post the form data to nodejs. And from then i accepting that data in node and need to call an external api. For this purpose i am using axios. After receiving values from my api i have to send that value back to react application. And when i run my code in postman, the output is {}. I think that i am not getting values from my api but dont know how to resolve this. And i am new to these technologies. Someone pls help me to sort out this problem. Thanking you in advance. Here is my what i have tried so far.
const express = require('express');
const axios = require('axios');
const router = express.Router();
const request = require('request');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended : false}));
router.get('/', (req, res) => {
res.send(" Express Homepage is running...");
});
async function callApi(emailid, pswd) {
return axios({
method:'post',
url:'http://51.X.X/api/login',
data: {
"email": `${emailid}`,
"password": `${pswd}`
},
headers: {'Content-Type': 'application/json' }
})};
callApi().then(function(response){
return response.data;
})
.catch(function(error){
console.log(error);
})
app.post('/api/login', (req, res) => {
let emailid = String(req.body.email);
let pswd = String(req.body.password);
const data = callApi(emailid, pswd);
if(data) {
res.send(data);
}else {
res.json({msg : " Response data not recieved.."})
}
});
use async/await syntax to handle asynchronous calls
app.post('/api/login', async (req, res) => {
let emailid = String(req.body.email);
let pswd = String(req.body.password);
const data = await callApi(emailid, pswd);
if(data) {
res.send(data);
}else {
res.json({msg : " Response data not recieved.."})
}
});
The problem is you are not waiting for async call to finish.
use async-await as mentioned in official doc https://www.npmjs.com/package/axios
function callAPI(){
const response = await axios({
method:'post',
url:'http://51.X.X/api/login',
data: {
"email": `${emailid}`,
"password": `${pswd}`
},
headers: {'Content-Type': 'application/json' }
})};
return response
}
app.post('/api/login', async (req, res) => {
let emailid = String(req.body.email);
let pswd = String(req.body.password);
//add try catch to catch exception
const data = await callApi(emailid, pswd);
if(data) {
//check for response from axios in official doc and send what data you
want to send
res.send(data);
}else {
res.json({msg : " Response data not recieved.."})
}
});
I'm learning graphQL with React native and using the apollo client. I'm experimenting with some code that has a simple login screen and I'm trying to check my understanding of the cache. My graphql client code is below. By turning on the debug for the persistCache I see the line when use CMD + R to reload an iOS simulator with expo. This suggests the cache is working.
[apollo-cache-persist] Restored cache of size 29
My question is what else is needed to complete the overall process of not needing to login again? I assume I need to maintain state on whether it's logged in and not show the login screen. I'm after some examples which show this.
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 5000,
jitter: true
},
attempts: {
max: Infinity,
retryIf: (error = {}) => {
return error.statusCode > 299 || !error.statusCode
}
}
});
const formatObject = data => _.isObject(data) ? JSON.stringify(data) : data;
const formatGraphQLError = err =>
`Message: ${err.message}, Location: ${formatObject(
err.locations
)}`;
const errorLink = onError(({ networkError = "", graphQLErrors = [] } = {}) => {
if (networkError)
console.log(`[Network Error]: ${networkError}`);
if (graphQLErrors.length)
graphQLErrors.map(formatGraphQLError).forEach(err => console.log(`[GraphQL Error]: ${err}`))
});
const authLink = setContext(async (_, { headers }) => {
const token = await Auth.token();
if (token)
return {
headers: {
...headers,
authorization: `Bearer ${token}`
}
};
else return { headers };
});
const httpLink = new HttpLink({
uri: Config.apiUrl
});
const cache = new InMemoryCache();
// Set up cache persistence.
persistCache({
cache,
storage: AsyncStorage,
trigger: 'background',
debug: true
});
const logLink = new ApolloLink((operation, forward) => {
console.log("Running GraphQL query or mutation");
return forward(operation);
});
//--
//-- Combine the links in your required order.
//--
let _notifications = 42;
const client = new ApolloClient({
resolvers: {
Query: {
permission: async (_, { type }) => await Permissions.askAsync(type),
token: async () => await Auth.token(),
notifications: () => _notifications
},
Mutation: {
login: async (_, { email, password }) => {
return await Auth.login(email, password)
},
updateNotifications: async (_, { notifications }) => _notifications = notifications
}
},
link: ApolloLink.from([
logLink,
retryLink,
errorLink,
authLink,
httpLink
]),
cache
});
export default client;
I am using Robin Wieruch's fullstack boilerplate but it is missing authentication for subscriptions. It uses JWT token for sessions and it is working fine for http but for ws auth is completely missing.
I need to pass user trough context for subscriptions as well, I need session info in subscriptions resolver to be able to decide weather I should fire subscription or not.
I did search Apollo docs, I saw I should use onConnect: (connectionParams, webSocket, context) function, but there is no fully functional fullstack example, I am not sure how to pass JWT from client to be able to get it in webSocket object.
Here is what I have so far:
Server:
import express from 'express';
import {
ApolloServer,
AuthenticationError,
} from 'apollo-server-express';
const app = express();
app.use(cors());
const getMe = async req => {
const token = req.headers['x-token'];
if (token) {
try {
return await jwt.verify(token, process.env.SECRET);
} catch (e) {
throw new AuthenticationError(
'Your session expired. Sign in again.',
);
}
}
};
const server = new ApolloServer({
introspection: true,
typeDefs: schema,
resolvers,
subscriptions: {
onConnect: (connectionParams, webSocket, context) => {
console.log(webSocket);
},
},
context: async ({ req, connection }) => {
// subscriptions
if (connection) {
return {
// how to pass me here as well?
models,
};
}
// mutations and queries
if (req) {
const me = await getMe(req);
return {
models,
me,
secret: process.env.SECRET,
};
}
},
});
server.applyMiddleware({ app, path: '/graphql' });
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
const isTest = !!process.env.TEST_DATABASE_URL;
const isProduction = process.env.NODE_ENV === 'production';
const port = process.env.PORT || 8000;
httpServer.listen({ port }, () => {
console.log(`Apollo Server on http://localhost:${port}/graphql`);
});
Client:
const httpLink = createUploadLink({
uri: 'http://localhost:8000/graphql',
fetch: customFetch,
});
const wsLink = new WebSocketLink({
uri: `ws://localhost:8000/graphql`,
options: {
reconnect: true,
},
});
const terminatingLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return (
kind === 'OperationDefinition' && operation === 'subscription'
);
},
wsLink,
httpLink,
);
const authLink = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => {
const token = localStorage.getItem('token');
if (token) {
headers = { ...headers, 'x-token': token };
}
return { headers };
});
return forward(operation);
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) => {
console.log('GraphQL error', message);
if (message === 'UNAUTHENTICATED') {
signOut(client);
}
});
}
if (networkError) {
console.log('Network error', networkError);
if (networkError.statusCode === 401) {
signOut(client);
}
}
});
const link = ApolloLink.from([authLink, errorLink, terminatingLink]);
const cache = new InMemoryCache();
const client = new ApolloClient({
link,
cache,
resolvers,
typeDefs,
});
You need to use connectionParams to set the JWT from the client-side. Below is the code snippet using the angular framework:
const WS_URI = `wss://${environment.HOST}:${environment.PORT}${
environment.WS_PATH
}`;
const wsClient = subscriptionService.getWSClient(WS_URI, {
lazy: true,
// When connectionParams is a function, it gets evaluated before each connection.
connectionParams: () => {
return {
token: `Bearer ${authService.getJwt()}`
};
},
reconnect: true,
reconnectionAttempts: 5,
connectionCallback: (error: Error[]) => {
if (error) {
console.log(error);
}
console.log('connectionCallback');
},
inactivityTimeout: 1000
});
const wsLink = new WebSocketLink(wsClient);
In your server-side, you are correct, using onConnect event handler to handle the JWT. E.g.
const server = new ApolloServer({
typeDefs,
resolvers,
context: contextFunction,
introspection: true,
subscriptions: {
onConnect: (
connectionParams: IWebSocketConnectionParams,
webSocket: WebSocket,
connectionContext: ConnectionContext,
) => {
console.log('websocket connect');
console.log('connectionParams: ', connectionParams);
if (connectionParams.token) {
const token: string = validateToken(connectionParams.token);
const userConnector = new UserConnector<IMemoryDB>(memoryDB);
let user: IUser | undefined;
try {
const userType: UserType = UserType[token];
user = userConnector.findUserByUserType(userType);
} catch (error) {
throw error;
}
const context: ISubscriptionContext = {
// pubsub: postgresPubSub,
pubsub,
subscribeUser: user,
userConnector,
locationConnector: new LocationConnector<IMemoryDB>(memoryDB),
};
return context;
}
throw new Error('Missing auth token!');
},
onDisconnect: (webSocket: WebSocket, connectionContext: ConnectionContext) => {
console.log('websocket disconnect');
},
},
});
server-side: https://github.com/mrdulin/apollo-graphql-tutorial/blob/master/src/subscriptions/server.ts#L72
client-side: https://github.com/mrdulin/angular-apollo-starter/blob/master/src/app/graphql/graphql.module.ts#L38