I am using graphql-express to create an endpoint where I can execute graphql queries in. Although I am using Sequelize with a SQL database it feels wrong to use it directly from the server outside of my graphql resolve functions. How do I go about querying my graphql API from the same server as it was defined in?
This is how I set up my graphql endpoint:
const express = require('express');
const router = express.Router();
const graphqlHTTP = require('express-graphql');
const gqlOptions = {
schema: require('./schema')
};
router.use('/', graphqlHTTP(gqlOptions));
modules.exports = router;
Basically what I want is to be able to do something like this:
query(`
{
user(id: ${id}) {
name
}
}
`)
How would I create this query function?
GraphQL.js itself does not require a http server to run. express-graphql is just a helper to mount the query resolver to a http endpoint.
You can pass your schema and the query to graphql, it'll return a Promise that'll resolve the query to the data.
graphql(schema, query).then(result => {
console.log(result);
});
So:
const {graphql} = require('graphql');
const schema = require('./schema');
function query (str) {
return graphql(schema, str);
}
query(`
{
user(id: ${id}) {
name
}
}
`).then(data => {
console.log(data);
})
I would like to complete the answer from #aᴍɪʀ by providing the pattern for properly doing a query / mutation with parameters:
const params = {
username: 'john',
password: 'hello, world!',
userData: {
...
}
}
query(`mutation createUser(
$username: String!,
$password: String!,
$userData: UserInput) {
createUserWithPassword(
username: $username,
password: $password,
userData: $userData) {
id
name {
familyName
givenName
}
}
}`, params)
This way, you don't have to deal with the string construction bits " or ' here and there.
Thanks for the other answers, this is for Nextjs inside getServerSideProps, getStaticProps, getStaticPaths and getStaticProps, includes context for MongoDB. Need this because if you have your graphql sever in api route, when you build it wont build because your server in api route is not running.
Mongo file: plugin/zDb/index:
import {MongoClient} from "mongodb"
export const connectToDatabase = async() => {
const client = new MongoClient(process.env.MONGODB_URI, {useNewUrlParser: true, useUnifiedTopology: true})
let cachedConnection
if(cachedConnection) return cachedConnection
try {
const connection = await client.connect()
cachedConnection = connection
return connection
} catch(error) {
console.error(error)
}
}
export const mongoServer = async() => {
const connect = await connectToDatabase()
return connect.db(process.env.DB_NAME)
}
In pages folder, eg index.js file homepage:
import {graphql} from 'graphql'
import {schema} from '#/plugin/zSchema/schema'
import {mongoServer} from '#/plugin/zDb/index'
async function query(source, variableValues) {
return graphql({schema, source, contextValue: {mongo: await mongoServer()}, variableValues})
}
export async function getServerSideProps(ctx) {
const listingCurrent = await query(`query($keyField: String, $keyValue: String) {
ListingRQlistingListKeyValue(keyField: $keyField, keyValue: $keyValue) {
address
urlSlug
imageFeature {
photoName
}
}
}`, {
keyField: 'offerStatus'
, keyValue: 'CURRENT'
})
return {props: {
listingCurrent: listingCurrent.data.ListingRQlistingListKeyValue
}
}
}
Please note: the graphql call field names is from: https://github.com/graphql/graphql-js/blob/fb27b92a5f66466fd8143efc41e1d6b9da97b1f4/src/graphql.js#L62
export type GraphQLArgs = {|
schema: GraphQLSchema,
source: string | Source,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?ObjMap<mixed>,
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>,
|};
And my schema file: plugin/zSchema/schema.js
import { makeExecutableSchema } from '#graphql-tools/schema'
import {resolvers} from '#/plugin/zSchema/resolvers'
import {typeDefs} from '#/plugin/zSchema/typeDefs'
export const schema = makeExecutableSchema({resolvers, typeDefs})
The #/plugin folder: I'm using this in root file called jsconfig.json, and I put all my folders inside root/plugin, and I call it with #/plugin. You can use your own folder structure importing them as how you normally do it.
{
"compilerOptions": {
"baseUrl": "."
, "paths": {
"#/*": ["./*"]
}
}
}
Related
I am new to Unit Testing and wanted to stub dynamodb-onetable library. I was trying to stub getData() from getDetails.ts file but it shows that "OneTableArgError: Missing Name Property". Because this getProjectDetails() contain new Table() class.
How to stub dynamodb-onetable so that I can get data in dataDetails variable. I was doing something like this in getEmp.spec.ts
dataDetailsStub = sinon.stub(DataService , "getData");
------lambda.ts
import { DynamoDBClient } from '#aws-sdk/client-dynamodb';
import Dynamo from 'dynamodb-onetable/Dynamo';
export const client = new Dynamo({
client: new DynamoDBClient({
region: REGION, }),
});
-------DataService.ts
import { client } from '../lambda';
const workspaceTable = new Table({
client,
name: TableName,
schema,
logger: true,
partial: false,
});
const projectDetail = workspaceTable.getModel('empDetail');
export default class **DataService** {
static getData = async (empId: string, type: string) => {
const params = {
projectId,
type
};
const response = await empDetail.find(params);
logger.trace('response', { response });
return response; };
}
------getDetails.ts
const dataDetails= await DataService.getData(
empId,
'EMPLOYEE-SAVEDATA'
);
I was trying to stub the DataService.getData() but getting error saying "OneTableArgError: Missing "name" property". I want to get data in dataDetailsStub whatever i am sending while mocking the getData()
const dataDetailsStub = sinon.stub(DataService , "getData").return({emp object});
Can anyone help me out on this. I'm really got stuck in this. Thanks in advance
I tried to use Apollo Federation and I create an user service and gateway. User service is working alone fine but Gateway is giving an error:
Error: A valid schema couldn't be composed. The following composition errors were found:
Syntax Error: Unexpected character: U+0130.
at IntrospectAndCompose.createSupergraphFromSubgraphList
Here is my code;
GATEWAY
import { ApolloGateway, IntrospectAndCompose } from "#apollo/gateway";
import { ApolloServer } from "#apollo/server";
import { expressMiddleware } from "#apollo/server/express4";
import { ApolloServerPluginDrainHttpServer } from "#apollo/server/plugin/drainHttpServer";
import express from "express";
import http from "http";
import { json } from "body-parser";
const startServer = async () => {
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: "userService", url: "http://localhost:4001" }
],
}),
});
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
gateway,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
await new Promise<void>((resolve) =>
httpServer.listen({ port: 4000 }, resolve)
);
console.log(`🚀 Server ready at http://localhost:4000/graphql`);
};
startServer();
USER SERVICE
index.ts
import { ApolloServer } from "#apollo/server";
import { buildSubgraphSchema } from "#apollo/subgraph";
import gql from "graphql-tag";
import { resolvers } from "./graphql/resolver";
import { startStandaloneServer } from "#apollo/server/standalone";
import { readFileSync } from "fs";
const mongoose = require("mongoose");
const typeDefs = gql(
readFileSync("src/graphql/types/user.graphql", { encoding: "utf-8" })
);
import { DB_URI } from "../database-config";
require("dotenv").config();
const createDatabaseConnection = async () => {
try {
await mongoose.connect(DB_URI);
console.log("User Service: Connected to DB");
} catch (error) {
console.log(error);
}
};
const startUserServiceServer = async () => {
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
await createDatabaseConnection();
const { url } = await startStandaloneServer(server, {
listen: { port: 4001 },
});
console.log(`User Service: Server ready at ${url}`);
};
startUserServiceServer();
user.graphql
extend schema
#link(url: "https://specs.apollo.dev/federation/v2.0", import: ["#key"])
type Query {
me: User
}
type Mutation {
addUser(fields: UserInput): User
updateUser(fields: UserInput): User
}
type User #key(fields: "id") {
id: ID
fullName: String
phone: String
avatarURL: String
password: String
email: String
address: String
createdAt: String
updatedAt: String
}
input UserInput {
id: ID
fullName: String
phone: String
avatarURL: String
password: String
email: String
address: String
}
I tried to change encoding style of user.graphl as utf16lebut it didn't work. Also I use Turkish as a default language on my computer. When I switched to English the problem was gone but I couldn't understand what the problem is.
The Name in GraphQL is defined to be limited to the regular expression /[_A-Za-z][_0-9A-Za-z]*/.
The Turkish captial I (U+0130 or İ) does not match this regular expression and is considered invalid.
[source: GraphQL Spec]
I am creating a server less function with having crud operation on dynamoDb with aws appsync GraphQl and done configuration using aws configure and deployed on the cloud.After deploying trying to query in app sync getting error Runtime.importModuleError.
In the lambda console layer is having 0
The AWS Cloud Development Kit (AWS CDK) is an open-source software development framework to define your cloud application resources using familiar programming languages
The AWS SDK for Java simplifies use of AWS Services by providing a set of libraries that are
consistent and familiar for Java developers. It provides support for API lifecycle consideration such as credential management, retries, data marshaling, and serialization
import * as cdk from 'aws-cdk-lib';
import * as appsync from '#aws-cdk/aws-appsync-alpha';
import {aws_dynamodb as dynamodb} from 'aws-cdk-lib';
import * as lambda from "aws-cdk-lib/aws-lambda";
// import * as sqs from 'aws-cdk-lib/aws-sqs';
export class BackendStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const api = new appsync.GraphqlApi(this, 'Api', {
name: 'demo',
schema: appsync.Schema.fromAsset('graphql/schema.gql'),
authorizationConfig: {
defaultAuthorization: {
authorizationType: appsync.AuthorizationType.IAM,
},
},
xrayEnabled: true,
});
//lambda function for
const Lambda= new lambda.Function(this, "Lambda", {
runtime: lambda.Runtime.NODEJS_16_X,
code: lambda.Code.fromAsset("lambda"),
handler: "index.handler",
timeout: cdk.Duration.seconds(30),
})
const table=new dynamodb.Table(this, "Table", {
partitionKey: {
name: "id",
type: dynamodb.AttributeType.STRING,
}
})
const lambdaDs=api.addLambdaDataSource("lambdaDatasource", Lambda)
lambdaDs.createResolver({
typeName: "Query",
fieldName: "getTodo",
})
lambdaDs.createResolver({
typeName: "Mutation",
fieldName: "addTodo",
})
lambdaDs.createResolver({
typeName: "Mutation",
fieldName: "deleteTodo",
})
lambdaDs.createResolver({
typeName: "Mutation",
fieldName: "updateTodo",
})
table.grantFullAccess(Lambda)
Lambda.addEnvironment('TABLE_NAME', table.tableName);
}
and used dynamoDB SDK for the crud operation and getting error in the app sync graphql query that Runtime.importModuleError
import { getTodo } from './getTodo'
import { updateTodo } from './update'
import { addTodo } from "./addTodo"
import { deleteTodo } from './delete'
import todo from "./todo"
type AppSyncEvent = {
info: {
fieldName: string
},
arguments: {
todoId: string,
todo: todo
}
}
exports.handler = async (event: AppSyncEvent) => {
switch (event.info.fieldName) {
case "getTodo":
return await getTodo();
case "createTodo":
return await addTodo(event.arguments.todo);
case "updateTodo":
return await updateTodo(event.arguments.todo);
case "deleteTodo":
return await deleteTodo(event.arguments.todoId);
default:
return null;
}
}
import { ScanCommand} from "#aws-sdk/client-dynamodb";
import { dynamoDb} from "./dbClient";
export const getTodo = async () => {
const params = {
TableName: process.env.TABLE_NAME,
};
const command = new ScanCommand(params);
try {
const result = await dynamoDb.send(command)
if (result.Items) {
return result.Items;
} else {
return null;
}
} catch (dbError) {
console.log(dbError);
return null;
}
}
import * as AWS from "#aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient } from "#aws-sdk/lib-dynamodb";
export const REGION = "ap-south-1"; // For example, "us-east-1".
// Create an Amazon DynamoDB service client object.
export const ddbClient = new AWS.DynamoDB({ region: REGION });
const marshallOptions = {
// Whether to automatically convert empty strings, blobs, and sets to `null`.
convertEmptyValues: false, // false, by default.
removeUndefinedValues: false, // false, by default.
convertClassInstanceToMap: false, // false, by default.
};
const unmarshallOptions = {
// Whether to return numbers as a string instead of converting them to native JavaScript numbers.
wrapNumbers: false, // false, by default.
};
const translateConfig = { marshallOptions, unmarshallOptions };
// Create the DynamoDB document client.`
const dynamoDb = DynamoDBDocumentClient.from(ddbClient, translateConfig);
export { dynamoDb };
Using Cloud Formation CDK for the execution of the Lambdas and Dynamo Db
I tried changing "type" :"module" but it gave me a typescript extension error.I replaced the following import type with require import it resolved
import {dynamodbClient} from aws-sdk/dyanmodb-Client
var Aws =require("aws-sdk")
Above worked for me
so it was an error for import, I was importing dynamodb libraries using import then i changed it to require (es5) import it resolved my error
When I am trying to run server with following configuration I receive an error:
Error: "createUser" defined in resolvers, but has invalid value "function (userInput) {...
The resolver's value must be of type object.
index.ts
const schema = loadSchemaSync(join(__dirname, './schema/**.graphql'), {
loaders: [new GraphQLFileLoader()]
})
const schemaWithResolvers = addResolversToSchema({
schema,
resolvers: {
...UserResolvers
}
})
.graphql schema
# import User, UserInput from "User.graphql"
type RootQueries {
user(id: String!): User
}
type RootMutations {
createUser(userInput: UserInput): User
}
schema {
query: RootQueries
mutation: RootMutations
}
resolvers
const UserResolvers = {
async createUser(userInput: UserInput) {
// some code
}
}
export { UserResolvers }
You're probably looking for
const schemaWithResolvers = addResolversToSchema({
schema,
resolvers: {
RootMutations: UserResolvers
}
})
as resolvers are usually grouped by the type they appear on.
I have the following Azure Function written in typescript
import { createConnection, getConnectionManager } from 'typeorm';
import { ApolloServer, gql } from 'apollo-server-azure-functions';
import { buildSchemaSync, buildSchema } from 'type-graphql';
import { GraphQLSchema } from 'graphql';
import { AzureFunction, Context, HttpRequest } from '#azure/functions';
import 'reflect-metadata';
import ProjectResolver from './data-layer/project/project.resolver';
import TaskResolver from './data-layer/task/task.resolver';
import { Project } from './models/entity/project/project.model';
import { Task } from './models/entity/task/task.model';
const typeDefs = gql`
type Project {
projectId: ID!
name: String
projectHandler: String
documentFolderId: Int
tasks: [Task]
}
type Task {
taskId: ID!
title: String
primarySearchEntityId: String
project: Project
}
type Query {
projects: [Project]
tasks: [Task]
}
`;
let ready = false;
// first promise
let schema: GraphQLSchema;
buildSchema({
resolvers: [
ProjectResolver,
TaskResolver
]
}).then(success => {
schema = success;
ready = true;
}).catch(() => {
throw "Something failed"
});
while(!ready) {
}
ready = false;
//second promise
createConnection({
type: "mssql",
host: "xxx",
port: xxxx,
username: "xxxx",
password: "xxxx",
database: "xxxx",
entities: [
Project,
Task
],
extra: {
options: {
encrypt: true
},
},
synchronize: false,
logging: false
}).then(() => {
ready = true;
})
.catch(() => {
throw "Something failed"
});
while(!ready) {
}
const server = new ApolloServer({ typeDefs, schema, debug: true });
export default server.createHandler();
My Apollo Server in this case needs the schema to exist to be exported. The schema can't exist until the promise in "buildSchema" resolves, and the resolvers won't work until the database connection is established in "createConnection". Here is my struggle, async-await would fix this, but I can't await the promises because they are in a top-level module. I tried the while loop, but that is apparently a locking operation, so the promises won't resolve until the while loop is finished executing (sort of a catch 22 here). And that seems like a huge hack anyways. So is there a way at the top level, I can ensure both promises resolve BEFORE exporting my Apollo handler? There is a synchronous way to build the schema with the TypeGraphQL library I can use, but I'm a little hosed here waiting for my database connection to succeed? It seems that all of these technologies are compatible with all of the others, just not all 4 simultaneously.
I was able to get around with using an asynchronous context:
const server = new ApolloServer({
typeDefs,
resolvers,
context: async () => ({
db: await createConnection(...),
})
}));
Wrap your logic in a top-level async function.
Make await calls as needed.
Return a promise from the top-level function which resolves with ApolloServer's response.
...
let server: ApolloServer
const httpTrigger: AzureFunction = async function (context: Context) {
if (server === undefined) {
const conn = await createConnection(...)
const schema = await buildSchema(...)
server = new ApolloServer({
schema,
})
}
const apolloHandler = server.createHandler()
return new Promise((resolve, reject) => {
const originalDone = context.done
context.done = (error, result) => {
originalDone(error, result)
if (error) {
reject(error)
}
resolve(result)
}
apolloHandler(context, context.req!)
})
}
export default httpTrigger
Here's a starter repo with a full example of using Apollo Server, TypeGraphQL, TypeORM and Azure together: azure-function-graphql-typescript-starter (I'm the author)