I'm trying to create a basic chat-like app just for the sake of learning a few things.
I have set up a basic graphql server to handle connecting a user, and let them add a message. Now I'm trying to add some mechanism so that every user can see each others' messages added in real time. I'm new to GraphQL but it would seem subscriptions are what I should use.
Here's my server index.ts:
import { createServer } from 'http';
import jwt from 'jsonwebtoken';
import mongoose from 'mongoose';
import resolvers from 'modules/resolvers';
import typeDefs from 'modules/type-defs';
import { ApolloServer } from 'apollo-server-express';
import cookieParser from 'cookie-parser';
import express from 'express';
import cors from 'cors';
const PORT = 4000;
const getUser = (token: string) => {
// ...
};
const server = new ApolloServer({
context: ({ req, res }) => {
const token = req.cookies.jwt;
const currentUser = getUser(token);
return { currentUser, req, res };
},
resolvers,
subscriptions: {
onConnect: async (connectionParams, webSocket, context) => {
console.log(`Subscription client connected using Apollo server's built-in SubscriptionServer.`)
},
onDisconnect: async (webSocket, context) => {
console.log(`Subscription client disconnected.`)
},
},
typeDefs,
});
const app = express();
const httpServer = createServer(app);
app.use(cors({ credentials: true, origin: 'http://localhost:3000' }));
app.use(cookieParser());
server.applyMiddleware({ app, cors: false });
server.installSubscriptionHandlers(httpServer);
httpServer.listen(PORT, () => {
console.log(`Server ready at http://localhost:${PORT}${server.graphqlPath}`);
console.log(`Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`);
});
mongoose.connect('mongodb://127.0.0.1:27017/irc', {
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
});
What I'm trying to do is set up something as simple as possible on the client side (javascript/react) without having to rely on a lib like apollo-client.
I managed to use simple fetch calls to send queries/mutations and was expecting to be able to use subscriptions in a "simple" way too. Apollo-client seems over complicated for what I'm trying to do and I'd like to understand how it actually works - but every tutorial on subscriptions seem to use this lib...
I don't really understand what my server is actually doing regarding subscriptions, and I thought I'd configured it to listen to websockets connections but I'm not so sure anymore.
I tried sending a basic message just to see what would happen:
const ws = new WebSocket('ws://localhost:4000/graphql');$
ws.onopen = event => {
ws.send('Lorem ipsum dolor sit amet.');
};
... but even though my chrome's network tab seems to indicate that everything went fine, my server does not seem to care about my message since nothing gets logged.
Could someone please explain if it's possible to use basic web sockets to use apollo-server's subscriptions? And how?
Thanks
Apollo Server and Apollo Client both use subscriptions-transport-ws under the hood to handle subscriptions. subscriptions-transport-ws uses WebSocket as a transport for exchanging messages between the server and the client. These messages have a specific format used by the library -- in order to use only WebSocket on the client-side, you'd have to send the same sort of messages.
You could inspect the source code to determine what sort of messages are being sent and when. The better option, though, would be to create an instance of SubscriptionClient and utilize its methods. Since usage of the library isn't particularly well documented, you'd have to stumble your way through but it should be possible.
If you're new to GraphQL, though, you should stick with Apollo Client since it's documentation is fairly good (see here) and subscriptions can be pretty complicated to set up, especially once you add authentication.
Related
is anyway to use walletconnect with etherejs ?
the demos are nice but they are with wagmi
i cant port all project from etherjs to wagmi
i need this feature this button connect disconect and possibility to use only few networks/chainids
import { arbitrum, mainnet, polygon } from "wagmi/chains";
https://docs.walletconnect.com/2.0/web3modal/react/installation
i this this exactly but with ETHERSJS
const { provider } = configureChains(chains, [
walletConnectProvider({ projectId: "<YOUR_PROJECT_ID>" }),
]);
const wagmiClient = createClient({
autoConnect: true,
connectors: modalConnectors({ appName: "web3Modal", chains }),
provider,
});
// Web3Modal Ethereum Client
const ethereumClient = new EthereumClient(wagmiClient, chains);
I found official example for WalletConnect v2 integration with Ethers.js
See https://github.com/WalletConnect/web-examples/blob/main/dapps/react-dapp-v2-with-ethers/src/contexts/ClientContext.tsx
Bad thing is that it's more complex than one with Wagmi. You have to connect lot of WalletConnect events to keep session info up to date, or to reset connection state.
Also official example (ClientContext.tsx) is IMO overusing React state which is not ideal. Would be nice to have official Ethers wrapper (not React, or other UI library dependent).
But it is definitely useful example to make Ethers integration work.
I am wondering why the Fetch API in javascript isn't call the endpoint I created in express. When I start my server and go to the '/characters' endpoint, it returns correctly formatted JSON.
Here is my express index.js
const app = express();
const PORT = 3000;
const charactersRoute = require('./routes/characters');
//Characters Route
app.use('/characters', charactersRoute)
app.listen(PORT, function(err) {
if(err) console.log(err);
console.log(`Server is listening on port ${PORT}`)
})
Here is my Characters Route
const express = require('express'); // Web Framework
const https = require('https');
const router = express.Router();
const PORT = 3000;
// app.listen(PORT, function(err) {
// if(err) console.log(err);
// console.log(`Server is listening on port ${PORT}`)
// })
const api = 'https://www.breakingbadapi.com/api/characters/?limit=20';
router.get("/", function(req, res) {
https.get(api, (response) => {
console.log(response.statusCode);
response.on('data', (d) => {
const data = JSON.parse(d);
res.send(data);
})
// res.send("Running")
})
})
module.exports = router;
Here is my Characters.jsx file where I'm using Fetch
import React, {useEffect, useState} from 'react';
import Card from '#mui/material/Card';
import axios from 'axios';
export default function Character() {
const [data, setData] = useState();
useEffect(() => {
fetch('/characters')
.then(res => res.json())
.then(data => setData(data))
// console.log(data);
}, []);
}
When I run my front end and check the response I receive, it returns my index.html.
If I fetch the API URL itself, then it correctly returns the JSON in my frontend. But When I try to fetch the API endpoint I created in express, I don't get any data. Any suggestions?
You did not set the endpoint for the fetch function. It doesn't know what API '/characters' is. It is similar to saying the house number but not telling the street, you don't know where to go. So you need to pass the absolute path to fetch to request data from the server, because the server is a different 'entity', it's not the same with your front-end. Therefore, you need to provide the full API URL. Or, if using axios, since I see you imported it above, you must set axios.defaults.baseURL = <API_URL>, (or provide the full URL in the request itself just like in fetch) and then make the request with the help of axios, not fetch.
Therefore your React code will look a little something like this:
import React, {useEffect, useState} from 'react';
import Card from '#mui/material/Card';
import axios from 'axios';
axios.defaults.baseURL = 'http://localhost:3000';
export default function Character() {
const [data, setData] = useState();
useEffect(() => {
const getData = async () => {
try {
const {data} = await axios.get('/characters');
setData(data);
} catch (err) {
console.error(err);
}
};
getData();
}, []);
}
But I suggest you create a different file with all the axios requests and there you set the baseUrl - that's a better practice.
I think is that you are not using cors in your app.
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
Try this, I hope it helps you!
Error pattern
This kind of error occurs when we send a request from the client-side to the server-side using a relative URL (/characters in this case). But the frontend app and the backend app run on 2 different ports.
Reason
When we use relative URLs, the URL will be concatenated with the current host (the frontend host, not the backend). Usually, we receive a 404 error because the resource doesn't exist.
Example
Backend Express app running on port 5000. React app running on port 3000 for development. In React app, if we send a request to /users, then the full URL is http://localhost:3000/users. The request goes to the React app, not the backend server. And we don't receive the desired output.
Action
You should use an absolute URL, something like: http://localhost:5000/users to send the request to your backend app. Consider saving the host part (http://localhost:5000) in a global variable in order to use it in multiple places in your frontend code. When you have different environments for the backend (DEV/STAGING/PRODUCTION), you can change the backend host in only 1 place.
I have a node express app that uses keycloak authentication to secure all API endpoints. Express middleware has been set up for authentication to make sure that each incoming request from the front end has the appropriate keycloak token. I need to make a post request from my node app to a third party backend API to subscribe users to an email service that uses a different authentication method which my middleware would not work with.
What would be the best practice for making a request from the third party API? I am considering creating a new express instance and use a separate middleware specific for that post request. Is this an ok thing to do or is there a better way?
Here is a simplified version of my node app. See the
index.js
import { authmware } from "./authmware";
import express from "express";
import { router } from "./router";
const app = express();
authmware(app);
router(app);
app.use((err, req, res, next) => {
logger.error(err.message);
const code = err.code ? err.code : 500;
const message = err.message ? err.message : "Internal Server Error";
res.status(code).json({ error: message, success: false });
});
export default app;
router.js
import express from "express";
import createProfile from "../../controllers/createProfile";
const router = express.Router();
router.post("/", createProfile);
export const router = (app) => {
app.use("/api/v1/createProfile", router);
};
controllers/createProfile.js
const createProfile = async (req, res) => {
// ... Do some stuff
// ** make request to a different api here **
await makeThirdPartyApiRequest();
}
How would I make this third party api request that uses a different style of authentication?
This is a very common use case. You can use 10 third party APIs in your node server and all having different authentication mechanisms irrespective of the auth you are using for your client requests.
await makeThirdPartyApiRequest();
// http request to API
// attach proper auth headers (API key / jwt / basic auth / oAuth token). This will be based on the authentication method the API is offering.
}
Update based on your recent comment:
The API should have some documentation on how to authenticate using the user key and secret key. For example: Google APIs just require you to send API key with request https://cloud.google.com/api-keys/docs/overview
I'm just getting started with GraphQL and I've worked mostly with featherjs to build REST APIs so far. There's the concept of hooks to run code before or after interacting with a service. I wanted to know if there's a way to achieve something similar in a graphql setup.
The specific stack I'm using is neo4j and graphql, express, and apollo server. Since neo4j has a graphql module, I'm not writing the general queries and mutations, just the model types.
For a concrete example, below is a basic API server. It's put together using docker compose and has users and applications. I would like to in general be able to run arbitrary code before or after a mutation with access to the model being modified. Things like more complex validation before, or side effects after.
Those things could be here, for example, before changing the state of an application to declined, make sure that a note is added. Or after changing the state to approved, send an email to the applicant.
I'm not sure how or where to implement this kind of thing, any help is appreciated! Or also if I'm thinking about graphql wrong and there's a better way of doing what I'm trying to do.
Thanks!
import http from 'http';
import express from 'express';
import neo4j from 'neo4j-driver';
import {Neo4jGraphQL} from '#neo4j/graphql';
import {gql, ApolloServer} from 'apollo-server-express';
import {ApolloServerPluginDrainHttpServer} from 'apollo-server-core';
const typeDefs=gql`
type User {
email: String!
applications: [Application!] #relationship(type: "APPLICANT", direction: OUT)
}
type Application {
user: User! #relationship(type: "APPLICANT", direction: IN)
state: String!
note: String
}
`;
const driver=neo4j.driver(
'neo4j://neo4j:7687',
neo4j.auth.basic('neo4j', 'password')
);
const {schema}=new Neo4jGraphQL({typeDefs, driver});
(async ()=>{
const app=express();
const server=http.createServer(app);
const apollo=new ApolloServer({
schema,
plugins: [ApolloServerPluginDrainHttpServer({httpServer: server})]
});
await apollo.start();
apollo.applyMiddleware({app});
await new Promise(r=>server.listen({port: 4000}, r));
console.log('GraphQL server listening at '+apollo.graphqlPath);
})();
Forgive me if this is something you shouldn't do, but I've looked around to see what is possible.
I want to verify that my express app has a middleware called/used for the app.
import express from 'express';
import cors from 'cors';
const app = express();
app.use(cors()); // <----- I WANT TO TEST THIS
app.get('/', (_req, res) => res.send({ hello: 'world' });
app.listen(5000, () => console.log(`Listening on port 5000`));
export default app;
Potential jest test
import app from './index.ts';
// This is a poor attempt to try and get this to work
test('test if CORS is implemented', () => {
const mockCors = jest.mock('cors');
const myApp = app;
expect(mockCors).toHaveBeenCalledTimes(1);
});
If anyone has the solution and if I should not be doing this, what is the reason behind it?
My guess is that you don't actually care that cors is called. Your client won't say to you "I want the cors middleware to be called". All the client should care about is the service that is provided, not how it's implemented. Your tests should do the same. If you decide one day to use another module for CORS, your tests shouldn't need to change, why would they?
My (personal) preferred approach is exactly that. Testing that given some input, my programs gives me the output I desire. Everything inside the program can be changed, as long as the behavior does not change. That will give you peace of mind when refactoring.
Also, for testing an Express app, you don't need a server, you just need the app. Actually starting the server can add complexity to your tests, since they might be hanging if you forget to close the server after them.
So in this example, the very first thing I would do is move everything you have in index.ts into a file called app.ts, and remove this line:
app.listen(5000, () => console.log(`Listening on port 5000`));
And in index.ts, only have this:
import app from './app.ts';
// Start the server
app.listen(5000, () => console.log(`Listening on port 5000`));
Then, I would use Supertest along with Jest, to make requests to that app:
npm i -D supertest
And then, in your test file, test what matters to the client:
import request from 'supertest';
import app from './app.js'; // Just import the app, not the server
describe("myApp", () => {
it('should implement CORS', async() => {
const { headers } = await request(app).get('/');
expect(headers['access-control-allow-origin']).toEqual('*');
});
});