Setting Up Apollo Server with subscriptions-transport-ws? - apollo

It seems like I have my server set up according to the Apollo docs at http://dev.apollodata.com/tools/apollo-server/setup.html. In my server/main.js file:
//SET UP APOLLO INCLUDING APOLLO PUBSUB
const executableSchema = makeExecutableSchema({
typeDefs: Schema,
resolvers: Resolvers,
connectors: Connectors,
logger: console,
});
const GRAPHQL_PORT = 8080;
const graphQLServer = express();
// `context` must be an object and can't be undefined when using connectors
graphQLServer.use('/graphql', bodyParser.json(), apolloExpress({
schema: executableSchema,
context: {}, //at least(!) an empty object
}));
graphQLServer.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
graphQLServer.listen(GRAPHQL_PORT, () => console.log(
`GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`
));
//SET UP APOLLO INCLUDING APOLLO PUBSUB
It prints out "GraphQL Server is now running on http://localhost:8080/graphql" to the terminal log indicating that the server was successfully initialized.
But at the top of my main_layout component, when I run this code:
import { Client } from 'subscriptions-transport-ws';
const wsClient = new Client('ws://localhost:8080');
...I get this console message:
WebSocket connection to 'ws://localhost:8080/' failed: Connection closed before receiving a handshake response
What am I missing?

You need to create a dedicated websocket server. It will run on a different port and the code to set it up is provided on the subscriptions-transport-ws package.
Take a look on the following code from GitHunt-API example:
https://github.com/apollostack/GitHunt-API/blob/master/api/index.js#L101-L134
Also you would see that this code is dependent on a class called SubscriptionManager. It is a class from a package called graphql-subscriptions also by the apollo team, and you can find an example of how to use it here:
https://github.com/apollostack/GitHunt-API/blob/master/api/subscriptions.js

TL;DR: You can use graphql-up to quickly get a GraphQL server with subscriptions support up and ready. Here's a more detailed tutorial on using this in combination with Apollo and the websocket client subscriptions-transport-ws.
Obtain a GraphQL Server with one click
Let's say you want to build a Twitter clone based on this GraphQL Schema in IDL syntax:
type Tweet {
id: ID!
title: String!
author: User! #relation(name: "Tweets")
}
type User {
id: ID!
name: String!
tweets: [Tweet!]! #relation(name: "Tweets")
}
Click this button to receive your own GraphQL API and then open the Playground, where you can add some tweets, query all tweets and also test out subscriptions.
Simple to use API
First, let's create a user that will be the author for all coming tweets. Run this mutation in the Playground:
mutation createUser {
createUser(name: "Tweety") {
id # copy this id for future mutations!
}
}
Here's how you query all tweets and their authors stored at your GraphQL server:
query allTweets {
allTweets {
id
title
createdAt
author {
id
name
}
}
}
Subscription support using websockets
Let's now subscribe to new tweets from "Tweety". This is the syntax:
subscription createdTweets {
Message(filter: {
mutation_in: [CREATED]
node: {
author: {
name: "Tweety"
}
}
}) {
node {
id
text
createdAt
sentBy {
id
name
}
}
}
}
Now create a new tab in the Playground and create a new Tweet:
mutation createTweet {
createTweet(
title: "#GraphQL Subscriptions are awesome!"
authorId: "<id-from-above>"
) {
id
}
}
You should see a new event popping up in your other tab where you subscribed before.

Here is a demo about using Apollo GraphQL, React & Hapi: https://github.com/evolastech/todo-react. It's less overwhelmed than GitHunt-React & GitHunt-API

Seems like you aren't actually making the websocket server. use SubscriptionServer. Keep in mind that it is absolutely NOT true that you have to have a dedicated websocket port (I thought this once too) as davidyaha says. I have both my normal queries and subs on the same port.
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { execute, subscribe } from 'graphql';
import { schema } from './my-schema';
// All your graphQLServer.use() etc setup goes here, MINUS the graphQLServer.listen(),
// you'll do that with websocketServer:
// Create WebSocket listener server
const websocketServer = createServer(graphQLServer);
// Bind it to port and start listening
websocketServer.listen(3000, () => console.log(
`Server is now running on http://localhost:3000`
));
const subscriptionServer = SubscriptionServer.create(
{
schema,
execute,
subscribe,
},
{
server: websocketServer,
path: '/subscriptions',
},
);

Related

Include Stripe in angular project

I am working on a website which uses angular for client and django for backend and I want to include stripe for payments. To configure stripe on the backend I have a microservice which runs on docker and accepts the following requests:
one for creating a stripe customer
another one for creating a payment
I configured stripe on the client using the provided form as follows:
export class PaymentComponent implements OnInit {
paymentHandler:any = null;
constructor(private checkoutService: CheckoutService) { }
ngOnInit() {
this.invokeStripe();
}
initializePayment(amount: number) {
const paymentHandler = (<any>window).StripeCheckout.configure({
key: 'pk_test_51MKU1wDo0NxQ0glB5HRAxUsR9MsY24POw3YHwIXnoMyFRyJ3cAV6FaErUeuEiWkGuWgAOoB3ILWXTgHA1CE9LTFr00WOT5U5vJ',
locale: 'auto',
token: function (stripeToken: any) {
console.log(stripeToken);
alert('Stripe token generated!');
paymentStripe(stripeToken);
}
});
const paymentStripe = (stripeTocken: any) => {
this.checkoutService.makePayment(stripeTocken).subscribe((data:any) => {
console.log(data)
})
}
paymentHandler.open({
name: 'Card Details',
description: 'Introduce the information from your card',
amount: amount * 100
});
}
invokeStripe() {
if(!window.document.getElementById('stripe-script')) {
const script = window.document.createElement("script");
script.id = "stripe-script";
script.type = "text/javascript";
script.src = "https://checkout.stripe.com/checkout.js";
script.onload = () => {
this.paymentHandler = (<any>window).StripeCheckout.configure({
key: 'pk_test_51MKU1wDo0NxQ0glB5HRAxUsR9MsY24POw3YHwIXnoMyFRyJ3cAV6FaErUeuEiWkGuWgAOoB3ILWXTgHA1CE9LTFr00WOT5U5vJ',
locale: 'auto',
token: function (stripeToken: any) {
console.log(stripeToken)
alert('Payment has been successfull!');
}
});
}
window.document.body.appendChild(script);
}
}
}
The makePayment method makes a request to the django server sending the stripe tocken.
From the server I need the above requests to the microservice.
My question is why do I need the configurations from the server as long as all I have to do to perform a payment is to make a request to the microservice? And where should I use the tocken.
I have also read about webhooks, but I don't really understand the concept and how to use this in my situation.
Also, do I need to test everything with angular cli? And why?
Thank you in advance!

How to initialize ApolloClient in SvelteKit to work on both SSR and client side

I tried but didn't work. Got an error: Error when evaluating SSR module /node_modules/cross-fetch/dist/browser-ponyfill.js:
<script lang="ts">
import fetch from 'cross-fetch';
import { ApolloClient, InMemoryCache, HttpLink } from "#apollo/client";
const client = new ApolloClient({
ssrMode: true,
link: new HttpLink({ uri: '/graphql', fetch }),
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache()
});
</script>
With SvelteKit the subject of CSR vs. SSR and where data fetching should happen is a bit deeper than with other somewhat "similar" solutions. The bellow guide should help you connect some of the dots, but a couple of things need to be stated first.
To define a server side route create a file with the .js extension anywhere in the src/routes directory tree. This .js file can have all the import statements required without the JS bundles that they reference being sent to the web browser.
The #apollo/client is quite huge as it contains the react dependency. Instead, you might wanna consider importing just the #apollo/client/core even if you're setting up the Apollo Client to be used only on the server side, as the demo bellow shows. The #apollo/client is not an ESM package. Notice how it's imported bellow in order for the project to build with the node adapter successfully.
Try going though the following steps.
Create a new SvelteKit app and choose the 'SvelteKit demo app' in the first step of the SvelteKit setup wizard. Answer the "Use TypeScript?" question with N as well as all of the questions afterwards.
npm init svelte#next demo-app
cd demo-app
Modify the package.json accordingly. Optionally check for all packages updates with npx npm-check-updates -u
{
"name": "demo-app",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build --verbose",
"preview": "svelte-kit preview"
},
"devDependencies": {
"#apollo/client": "^3.3.15",
"#sveltejs/adapter-node": "next",
"#sveltejs/kit": "next",
"graphql": "^15.5.0",
"node-fetch": "^2.6.1",
"svelte": "^3.37.0"
},
"type": "module",
"dependencies": {
"#fontsource/fira-mono": "^4.2.2",
"#lukeed/uuid": "^2.0.0",
"cookie": "^0.4.1"
}
}
Modify the svelte.config.js accordingly.
import node from '#sveltejs/adapter-node';
export default {
kit: {
// By default, `npm run build` will create a standard Node app.
// You can create optimized builds for different platforms by
// specifying a different adapter
adapter: node(),
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte'
}
};
Create the src/lib/Client.js file with the following contents. This is the Apollo Client setup file.
import fetch from 'node-fetch';
import { ApolloClient, HttpLink } from '#apollo/client/core/core.cjs.js';
import { InMemoryCache } from '#apollo/client/cache/cache.cjs.js';
class Client {
constructor() {
if (Client._instance) {
return Client._instance
}
Client._instance = this;
this.client = this.setupClient();
}
setupClient() {
const link = new HttpLink({
uri: 'http://localhost:4000/graphql',
fetch
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
return client;
}
}
export const client = (new Client()).client;
Create the src/routes/qry/test.js with the following contents. This is the server side route. In case the graphql schema doesn't have the double function specify different query, input(s) and output.
import { client } from '$lib/Client.js';
import { gql } from '#apollo/client/core/core.cjs.js';
export const post = async request => {
const { num } = request.body;
try {
const query = gql`
query Doubled($x: Int) {
double(number: $x)
}
`;
const result = await client.query({
query,
variables: { x: num }
});
return {
status: 200,
body: {
nodes: result.data.double
}
}
} catch (err) {
return {
status: 500,
error: 'Error retrieving data'
}
}
}
Add the following to the load function of routes/todos/index.svelte file within <script context="module">...</script> tag.
try {
const res = await fetch('/qry/test', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
num: 19
})
});
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
Finally execute npm install and npm run dev commands. Load the site in your web browser and see the server side route being queried from the client whenever you hover over the TODOS link on the navbar. In the console's network tab notice how much quicker is the response from the test route on every second and subsequent request thanks to the Apollo client instance being a singleton.
Two things to have in mind when using phaleth solution above: caching and authenticated requests.
Since the client is used in the endpoint /qry/test.js, the singleton pattern with the caching behavior makes your server stateful. So if A then B make the same query B could end up seeing some of A data.
Same problem if you need authorization headers in your query. You would need to set this up in the setupClient method like so
setupClient(sometoken) {
...
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: `Bearer ${sometoken}`
}
};
});
const client = new ApolloClient({
credentials: 'include',
link: authLink.concat(link),
cache: new InMemoryCache()
});
}
But then with the singleton pattern this becomes problematic if you have multiple users.
To keep your server stateless, a work around is to avoid the singleton pattern and create a new Client(sometoken) in the endpoint.
This is not an optimal solution: it recreates the client on each request and basically just erases the cache. But this solves the caching and authorization concerns when you have multiple users.

How to start apollo federation server only when all services are available

I want to start a federated apollo server:
const gateway = new ApolloGateway({
serviceList: [
... list of services
],
});
const startServer = async () => {
const gatewayConfig = await gateway.load();
const server = new ApolloServer({
...gatewayConfig,
subscriptions: false,
});
server.listen().then(({ url }) => {
console.log("Server running!");
});
};
startServer();
When I start the server and one of the services in the serviceList is available, the server starts and logs which services have failed. I want the server to only start when all the services are available, ie when one service is unavailable an error is thrown and the server stops. Any ideas how to do this?
Apollo can't do this as of writing this answer. The only solution is to monitor the availability manually and leverage apollo accordingly. I used apollo-server-express for this.
Below is a demonstration of how I managed to leverage my apollo gateway based on the availability of my services.
Basically, you wrap the middleware of your apollo server. This allows you to exchange your apollo server instance as well as throwing an error when they are not available.
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import bodyParser from 'body-parser'; // use express body-parser for convinience
// your list of federated services
const serviceList = [
{ name: 'service1', url: 'http://service1/graphql' }
];
// a variable to store the server. We will need to replace him when a service goes offline or comes back online again
let server = null;
// setup express
const app = express();
app.use(bodyParser.json());
app.use(customRouterToLeverageApolloServer); // defined below
// middleware to leverage apollo server
function customRouterToLeverageApolloServer(req, res, next) {
// if services are down (no apollo instance) throw an error
if(!server) {
res.json({ error: 'services are currently not available' });
return;
}
// else pass the request to apollo
const router = server.getMiddleware(); // https://www.apollographql.com/docs/apollo-server/api/apollo-server/#apolloservergetmiddleware
return router(req, res, next);
}
function servicesAreAvailable() {
// go through your serviceList and check availability
}
// periodically check the availability of your services and create/destroy an ApolloServer instance accordingly. This will also be the indication whether or not your services are available at the time.
// you might want to also call this function at startup
setInterval(() => {
if(servicesAreAvailable()) {
server = new ApolloServer({ ... });
}
else {
server = null;
}
}, 1000 * 60 * 5) // check every 5 minutes

How can I set a cookie with Relay?

I've got express js server code:
...
const server = new GraphQLServer({
typeDefs: `schema.graphql`,
resolvers,
context: context => {
let cookie = get(context, 'request.headers.cookie');
return { ...context, cookie, pubsub };
},
});
such that I can attach cookie to resolvers' requests:
...
method: 'GET',
headers: {
cookie: context.cookie,
},
Now I want to be able to use Relay (as a GraphQL client) and I want to be able to attach a cookie to Relay's requests as well.
I've found a similar question but it's not clear to me where can I insert that code:
Relay.injectNetworkLayer(
new Relay.DefaultNetworkLayer('/graphql', {
credentials: 'same-origin',
})
);
since I don't import Relay in Environment.js.
Update: I tried to add
import { Relay, graphql, QueryRenderer } from 'react-relay';
Relay.injectNetworkLayer(
new Relay.DefaultNetworkLayer('http://example.com/graphql', {
credentials: 'same-origin',
})
);
to a file where I send GraphQL queries (e.g., client.js), but it says that Relay is undefined.
Update #2: this repo looks interesting.

How do I restore the session ID for Express Session Authentication in a NativeScript app?

Buckle up, this one's a little bit complicated. I know that Express sends the browser a connect.sid cookie... and Passport uses this to deserialize the User on web requests. Not only that, but when I log in to my application from my NativeScript app (I'm running on a Pixel 2 emulator on a Windows PC, but I know it also works on iOS), the cookie seems to be correctly set and sent along with future web requests. I also understand how the application-settings API works, and that you can use this to store a user-identifying token for future boots of the application (so that I don't have to log in every time).
So here's where the disconnect occurs. Conceivably I can override the cookie in the request header if I have it stored, but nowhere can I find documentation on how to retrieve a cookie from the successful login request in nativescript.
Here's the code:
TokenSvc
import { Injectable } from "#angular/core";
import { getString, setString } from "application-settings";
export class TokenSvc {
static isLoggedIn(): boolean {
return !!getString("token");
}
static get token(): string {
return getString("token");
}
static set token(token: string) {
setString("token", token);
}
}
Login Component
(Note I am making an embarrassing attempt at getting the cookies from a new HttpHeaders instance... not sure why I thought that would work.)
#Component({
selector: "app-login",
moduleId: module.id,
templateUrl: "./login.component.html",
styleUrls: ["./login.component.scss"]
})
export class LoginComponent {
credentials: ILoginCredentials;
#ViewChild("password") password: ElementRef;
#ViewChild("handle") handle: ElementRef;
#ViewChild("confirmPassword") confirmPassword: ElementRef;
constructor(private page: Page, private router: Router, private AuthSvc: AuthSvc, private _store: Store<AppStore>) {
this.page.actionBarHidden = true;
this.credentials = {
email: "",
password: "",
cPassword: "",
handle: "",
publicName: ""
};
}
login() {
const loginCredentials: ICredentials = {
username: this.credentials.email,
password: this.credentials.password,
rememberMe: false
};
this.AuthSvc.login(loginCredentials).subscribe(
(payload) => {
console.log(payload);
if (payload.failure) {
alert(payload.failure);
} else {
// user!
let cookies = new HttpHeaders().get("Cookie");
console.log(cookies);
TokenSvc.token = cookies;
this._store.dispatch({ type: "SET_USER", payload: payload });
this.router.navigate(["/tabs"]);
}
}, () => alert("Unfortunately we were unable to create your account.")
);
}
}
The essential question here is... how do I persist a cookie-based session in NativeScript application-settings with a Node/Express back-end?
The essential answer is: you don't.
Prefer JWT, OAuth2 or any other token-based authentication method when it comes to mobile development. You can use the same authentication method for web too.
Store the user token using the secure storage and send the token along with any request made by the user.