Using sofa api with the most simple express server - express

Hi.
I have this most simple express graphql server.
I want to use sofa-api to make it "rest-able".
Two problems:
when you go to /api/hello it should say "Hello World!", now its null. the /graphql route does work correctly and return "Hello World!".
The rest swagger interface is not loading at /api
you can play with it here: https://stackblitz.com/edit/node-p6jnji?file=index.js,package.json
var schema = buildSchema(`
type Query{
hello:String
}`);
const openApi = OpenAPI({
schema,
info: {
title: 'Example API',
version: '3.0.0',
},
});
var root = {
hello: () => {
return 'Hello World!';
},
};
var app = express();
app.use(
'/graphql',
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
})
);
app.use(
'/api',
useSofa({
schema,
basePath: '/api',
onRoute(info) {
openApi.addRoute(info, {
basePath: '/api',
});
},
})
);
writeFileSync('./swagger.json', JSON.stringify(openApi.get(), null, 2));
app.listen(4400);
Thanks

There is no root value defined in Sofa.
In graphqlHttp call, you provide rootValue as an execution parameter but Sofa is not aware of that
I assume the most elegant way is to build your schema with the resolvers (root value)
something like this:
import { makeExecutableSchema } from '#graphql-tools/schema'
export const schema = makeExecutableSchema({ typeDefs, resolvers })
You can find more info here

Related

Apollo Server as Nuxt serverMiddleware

I've managed to have a express + Apollo Backend as a serverMiddleware in Nuxtjs.
Everything works fine(auth, cache, datasources, queries, mutations) but now I'm trying to get subscriptions(websockets) running and its giving me a hard time.
I tried this example https://www.apollographql.com/docs/apollo-server/data/subscriptions/#subscriptions-with-additional-middleware but even letting the httpServer listening didn't work.
This is my API file which I require through the nuxt.config.js with '~/api/index' :
module.exports = async () => {
const app = require('express')()
const server = await require("./apollo")() // apollo-server-express w/ typeDefs and resolvers
// apply Apollo to Express
server.applyMiddleware({ app });
console.log(`🚀 ApolloServer ready at ${server.graphqlPath}`);
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
console.log(`🚀 ApolloSubscriptions ready at ${server.subscriptionsPath}`);
return {
path: '/api',
handler: httpServer
}
}
Now my playground is giving me this error: "Could not connect to websocket endpoint ws://192.168.150.98:3000/api/graphql. Please check if the endpoint url is correct."
TypeDefs:
type Subscription {
postAdded: Post
}
type Post {
author: String
comment: String
}
type Query {
posts: [Post]
}
type Mutation {
addPost(author: String, comment: String): Post
}
Resolvers:
Query: {
posts(root, args, context) {
return Posts;
}
}
Mutation: {
addPost(root, args, context) {
pubsub.publish(POST_ADDED, { postAdded: args });
return Posts.add(args);
}
},
Subscription: {
postAdded: {
// Additional event labels can be passed to asyncIterator creation
subscribe: () => pubsub.asyncIterator([POST_ADDED]),
},
}
First question here, thank u in advance! :)
it can also be a little easier
1.
yarn add apollo-server-express
or
npm install apollo-server-express
create file ./server/index.js
import { ApolloServer, gql } from 'apollo-server-express'
// Construct a schema, using GraphQL schema language
const typeDefs = gql`
type Query {
hello: String
}
`
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
}
const server = new ApolloServer({ typeDefs, resolvers })
export default server
add in your nuxt.config.js
import server from './server'
export default {
// ... your nuxt config stuff
// ...
hooks: {
render: {
async before({
nuxt: {
server: { app },
},
}) {
await server.applyMiddleware({ app, path: '/api' })
console.log(`🚀 ApolloServer ready at /api`)
},
},
}
}
I found a hacky way to achieve it, import the code as a nuxt module:
import http from 'http'
export default function () {
this.nuxt.hook('render:before', async () => {
const server = require("./apollo")()
// apply Apollo to Express
server.applyMiddleware({ app: this.nuxt.renderer.app });
console.log(`🚀 ApolloServer ready at ${server.graphqlPath}`);
const httpServer = http.createServer(this.nuxt.renderer.app);
// apply SubscriptionHandlers to httpServer
server.installSubscriptionHandlers(httpServer);
console.log(`🚀 ApolloSubscriptions ready at ${server.subscriptionsPath}`);
// overwrite nuxt.server.listen()
this.nuxt.server.listen = (port, host) => new Promise(resolve => httpServer.listen(port || 3000, host || 'localhost', resolve))
// close this httpServer on 'close' event
this.nuxt.hook('close', () => new Promise(httpServer.close))
})
}
Tho I'm now using a probably more stable way, using nuxt programmatically!
With hapi instead of express, since express is giving me trouble compiling and not showing the loading-screen(progress of building).
Just use npx create-nuxt-app and create an app with a hapi server backend.
The code with hapi would look like this:
const consola = require('consola')
const Hapi = require('#hapi/hapi')
const HapiNuxt = require('#nuxtjs/hapi')
async function start () {
const server = require('./apollo/index')()
const app = new Hapi.Server({
host: process.env.HOST || '127.0.0.1',
port: process.env.PORT || 3000
})
await app.register({
plugin: HapiNuxt
})
app.route(await require('./routes')())
await server.applyMiddleware({
app,
path: '/graphql'
});
console.log(`🚀 ApolloServer ready at ${server.graphqlPath}`);
await server.installSubscriptionHandlers(app.listener)
console.log(`🚀 ApolloSubscriptions ready at ${server.subscriptionsPath}`);
await app.start()
consola.ready({
message: `Server running at: ${app.info.uri}`,
badge: true
})
}
process.on('unhandledRejection', error => consola.error(error))
start().catch(error => console.log(error))
Maybe i can help somebody
An easier way is to use the getMiddleware() method of Apollo Server Express:
Create a file under ./api/index.js:
const { ApolloServer, gql } = require('apollo-server-express')
const express = require('express')
const typeDefs = gql`
type Query {
hello: String
}
`
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
}
const server = new ApolloServer({ typeDefs, resolvers })
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use(server.getMiddleware())
module.exports = app
and then register it in ./nuxt.config.js:
{
// other nuxt config ...
serverMiddleware: [{ path: '/api', handler: '~/api/index.js' }],
}

Apollo Server Express: Request entity too large

I need to POST a large payload in a GraphQL mutation. How do I increase the body size limit of Apollo Server?
I'm using apollo-server-express version 2.9.3.
My code (simplified):
const myGraphQLSchema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: UserQuery,
},
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: () => ({
...UserMutations,
}),
}),
});
const apolloServer = new ApolloServer(schema: myGraphQLSchema);
const app = express();
app.use(apolloServer.getMiddleware({ path: '/graphql' });
Not exactly sure in which version it was added, but on 2.9.15 you can apply it in applyMiddleware function.
const apolloServer = new ApolloServer(someConfig);
apolloServer.applyMiddleware({
app,
cors: {
origin: true,
credentials: true,
},
bodyParserConfig: {
limit:"10mb"
}
});
Simply add an Express body parser before your Apollo server middleware:
import { json } from 'express';
app.use(json({ limit: '2mb' });
app.use(apolloServer.getMiddleware({ path: '/graphql' });
If you want to get fancy, you can have a separate body size limit for authenticated vs unauthenticated requests:
const jsonParsers = [
json({ limit: '16kb' }),
json({ limit: '2mb' }),
];
function parseJsonSmart(req: Request, res: Response, next: NextFunction) {
// How exactly you do auth depends on your app
const isAuthenticated = req.context.isAuthenticated();
return jsonParsers[isAuthenticated ? 1 : 0](req, res, next);
}
app.use(parseJsonSmart);
app.use(apolloServer.getMiddleware({ path: '/graphql' });

How do I create custom directives to protect my GraphQL API?

I want to use custom directives to protect my GraphQL API. More specifically, for specific GraphQL fields, I want to check if users have authorisation to query those fields when a request hits my GraphQL server.
The following links are articles that contain examples on achieving this objective.
Link 1: https://www.prisma.io/blog/graphql-directive-permissions-authorization-made-easy-54c076b5368e/
Link 2: https://codeburst.io/use-custom-directives-to-protect-your-graphql-apis-a78cbbe17355
However, both examples achieve this by first constructing their Schema using the GraphQL Schema Definition Language (below is a snippet from the repo for Link 2) that demonstrates how you can use custom directives to check if users have authorisation to query specific fields (such as "rating").
require('dotenv').config();
const express = require('express');
const graphqlHTTP = require('express-graphql');
const {
makeExecutableSchema
} = require('graphql-tools');
const {directiveResolvers} = require('./directives');
const {allProductsBySupplier, addProduct, product, suppliers} = require('./resolvers');
require('./auth');
const app = express();
const port = 8080;
const typeDefs = `
directive #isAuthenticated on QUERY | FIELD
directive #hasScope(scope: [String]) on QUERY | FIELD
type Product {
id: ID!
supplierId: ID!
sku: String
qty: Int
price: Int
parrot: String
rating: Int #hasScope(scope: ["read:rating"])
}
type Supplier {
id: ID!
name: String!
}
input ProductInput {
supplierId: ID!
sku: String!
qty: Int!
price: Int!
parrot: String!
rating: Int!
}
type Query {
allProductsBySupplier: [Product] #isAuthenticated
product: Product #isAuthenticated
suppliers: [Supplier]
}
type Mutation {
addProduct(input: ProductInput!): Product #hasScope(scope: ["add:product"])
}
`;
const resolvers = {
Query: {
allProductsBySupplier,
product,
suppliers
},
Mutation: {
addProduct
}
};
const schema = makeExecutableSchema({
typeDefs,
resolvers,
directiveResolvers
});
app.use(
'/graphql',
graphqlHTTP({
schema,
graphiql: true
})
);
app.listen(port);
console.log(`server running on localhost:${port}`);
I have constructed my API without using the GraphQL schema definition language as shown below. The following snippet has been extracted from the official graphql docs.
var express = require('express');
var graphqlHTTP = require('express-graphql');
var graphql = require('graphql');
// Maps id to User object
var fakeDatabase = {
'a': {
id: 'a',
name: 'alice',
},
'b': {
id: 'b',
name: 'bob',
},
};
// Define the User type
var userType = new graphql.GraphQLObjectType({
name: 'User',
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
}
});
// Define the Query type
var queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: userType,
// `args` describes the arguments that the `user` query accepts
args: {
id: { type: graphql.GraphQLString }
},
resolve: function (_, {id}) {
return fakeDatabase[id];
}
}
}
});
var schema = new graphql.GraphQLSchema({query: queryType});
var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
How can I create custom directives to check if users have authorisation to query specific fields if I have constructed my Schema without using the GraphQL Schema Definition Language?

How can I authenticate a GraphQL endpoint with Passport?

I have a GraphQL endpoint:
app.use('/graphql', graphqlHTTP(request => ({
graphiql: true,
schema
})));
I also have a Passport route for logging in (and handling the callback, since I'm using Google OAuth2):
this.app.get('/login', passport.authenticate('google'));
this.app.get('/auth/callback/google', ....
Passport add a user to the request, and all of the articles I can find online recommend authenticating in each of my GraphQL resolvers using that:
resolve: (root, args, { user }) => {
if (!user) throw new NotLoggedInError();
However it doesn't make sense to have to add that logic to every resolver when it applies to all of them, so I was hoping to somehow authenticate the entire endpoint.
The problem is that I'm not sure how to combine middleware. I tried the following but it just broke the endpoint:
app.use('/graphql', passport.authenticate('google'), graphqlHTTP(request => ({
graphiql: true,
schema
})));
I have the following working. Some issues I had were around making sure my google API was enabled and the proper scopes were enabled. I am also only using the passport middleware on the auth endpoints and using an isAuthenticated middleware to check if the session is authenticated and if not redirect to the auth endpoint. also putting the request object into the context so that it can be used by the resolver to potentially authorize the user. You would of course need to update the user lookup as I am just passing mock data.
import express from "express";
import graphqlHTTP from "express-graphql";
import passport from "passport";
import cookieParser from "cookie-parser";
import session from "express-session";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { buildSchema } from "graphql";
const PORT = 5000;
const data = [
{ id: "1", name: "foo1" },
{ id: "2", name: "foo2" },
{ id: "3", name: "foo3" },
];
const def = `
type Foo {
id: String!
name: String
}
type Query {
readFoo(id: String!): Foo
}
schema {
query: Query
}
`;
const schema = buildSchema(def);
const fieldMap = schema.getType("Query").getFields();
fieldMap.readFoo.resolve = (source, args) => {
return data.filter(({ id }) => id === args.id)[0] || null;
};
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((obj, done) => {
done(null, obj);
});
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: `http://localhost:${PORT}/auth/google/callback`,
},
(accessToken, refreshToken, profile, cb) => {
return cb(null, {
id: "1",
username: "foo#bar.baz",
googleId: profile.id,
});
}
)
);
function isAuthenticated(req, res, next) {
return req.isAuthenticated() ? next() : res.redirect("/auth/google");
}
const app = express();
app.use(cookieParser());
app.use(
session({
secret: "sauce",
resave: false,
saveUninitialized: false,
})
);
app.use(passport.initialize());
app.use(passport.session());
app.get("/auth/fail", (req, res) => {
res.json({ loginFailed: true });
});
app.get(
"/auth/google",
passport.authenticate("google", { scope: ["profile"] })
);
app.get(
"/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/auth/fail" }),
(req, res) => {
res.redirect("/graphql");
}
);
app.use(
"/graphql",
isAuthenticated,
graphqlHTTP((req) => ({
schema,
graphiql: true,
context: req,
}))
);
app.listen(PORT, () => {
console.log("Started local graphql server on port ", PORT);
});
vbranden's answer was excellent, and it is the basis of this answer. However, his answer has a lot of other code which obfuscates the solution a bit. I didn't want to mess with it, since it offers a more complete view of things, but hopefully this answer will be helpful in its own way by being more direct. But again, all credit for this solution belongs to vbranden (please upvote his answer accordingly).
If you make an isAuthenticated function with the appropriate signature (request, response, next) you can then "chain" that function in when you setup your GraphQL endpoint:
function isAuthenticated(req, res, next) {
return req.isAuthenticated() ?
next() :
res.redirect('/auth/google');
}
app.use(
'/graphql',
isAuthenticated,
graphqlHTTP(req => ({
schema,
graphiql: true,
context: req
}))
);

GraphQL. How to write resolver

I just started to get into GraphQL. I am using GraphQL.js and express. Right now I am trying to build a simple example using a hardcoded JSON as the data in my javascript file. I then want to use express middleware to listen to HTTP requests via curl or insomnia. In the middleware I want to extract the query using body-parser. Right now I am having trouble with resolvers.
Please have a look at my code.
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema, graphql } = require('graphql');
var bodyParser = require('body-parser');
var schema = buildSchema(`
type Product {
name: String!
price: Int!
}
type Query {
product(name: String): Product
}
`);
var products = {
'Mango': {
name: 'Mango',
price: 12,
},
'Apfel': {
name: 'Apfel',
price: 3,
},
};
resolvers = {
Query: {
product: (root, { name}) => {
return products[name];
},
},
};
var app = express();
app.use(bodyParser.text({ type: 'application/graphql' }));
app.post('/graphql', (req, res) => {
graphql(schema, req.body)
.then((result) => {
res.send(JSON.stringify(result, null, 2));
});
});
app.listen(4000);
This does not work. When I post a query using curl with
curl -XPOST -H "Content-Type: application/graphql" -d "{product(name: \"Apfel\"){name price}}" http://localhost:4000/graphql
I get the response {"data". {"product": null}}. The resolver doesn't get called. How can I do this correctly?
Can you try this?
var resolvers = {
product: (args) => {
return products[args.name];
},
};
app.post('/graphql', (req, res) => {
graphql(schema, req.body, resolvers)
.then((result) => {
res.send(JSON.stringify(result, null, 2));
});
});
I think this can solve your issue
I recomend watching FunFunFunction series episode focused on GraphQl:
GraphQl Basics
All of his episodes are quite interesting (and really fun)...