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
Related
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 have 3 domains that users can choose when they log in. Let's say I have domains dev, demo, and sandbox. I use Axios in which the domain instance is configured in the Boot.js file. The boot file is only triggered when the Vue instance is still not fired. Here is the boot file:
import { boot } from "quasar/wrappers";
import axios from "axios";
import { LocalStorage } from "quasar";
import { Platform } from "quasar";
let baseURL = LocalStorage.getItem("basepath");
let api = axios.create({ baseURL: baseURL });
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { axios, api };
Here is the script chunk of the login file:
<script>
import { LocalStorage } from "quasar";
export default {
data() {
return {
companyList: [{
id: "dev",
site: "https://dev.mycompany.com",
},
{
id: "demo",
site: "https://demo.mycompany.com",
},
{
id: "sandbox",
site: "https://sandbox.mycompany.com",
},
],
loginData: {
username: "",
password: "",
companyid: "",
},
};
},
methods: {
checkCompanyId(payload) {
let temp = this.companyList.find((o) => o.id == payload.toLowerCase());
if (temp) {
LocalStorage.set("basepath", temp.site); // <-- Here is the setting of the basepath
return true;
} else {
return false;
}
},
submitForm() {
const CompanyId = this.checkCompanyId(this.loginData.companyid);
if (CompanyId) {
this.loginProcess(this.loginData);
} else {
console.log('Company ID can't be found!')
}
}
},
}
}
</script>
The problem is the Local Storage is actually changed but the variable baseURL in the Boot.js file is not changed unless I reload the page. Is there possibly a way to reassign the variable baseURL whenever the local storage has changed?
So, I'm quite inspired by the two answers above. My boot file is not a vuex file, there is no way for me to make an action. Instead, I made a function that can be called when a site is chosen and exported it. Here is the function I implemented to change the variable in my boot file.
let changeBasepath = (basepath) => {
baseURL = basepath;
api = axios.create({ baseURL: baseURL });
};
My boot file after the change looks like this:
import { boot } from "quasar/wrappers";
import axios from "axios";
import { LocalStorage } from "quasar";
import { Platform } from "quasar";
let baseURL = LocalStorage.getItem("basepath");
let api = axios.create({ baseURL: baseURL });
let changeBasepath = (basepath) => {
baseURL = basepath;
api = axios.create({ baseURL: baseURL });
};
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { axios, api, changeBasepath };
Then, I imported the boot file and implemented the function. Here is the script chunk of my vue file looks like after the change:
<script>
import { LocalStorage } from "quasar";
import { changeBasepath } from "boot/api"; // <-- IMPORT IT
export default {
data() {
return {
companyList: [{
id: "dev",
site: "https://dev.mycompany.com",
},
{
id: "demo",
site: "https://demo.mycompany.com",
},
{
id: "sandbox",
site: "https://sandbox.mycompany.com",
},
],
loginData: {
username: "",
password: "",
companyid: "",
},
};
},
methods: {
checkCompanyId(payload) {
let temp = this.companyList.find((o) => o.id == payload.toLowerCase());
if (temp) {
LocalStorage.set("basepath", temp.site); // <-- Here is the setting of the basepath
changeBasepath(temp.site); // <-- IMPLEMENT IT
return true;
} else {
return false;
}
},
submitForm() {
const CompanyId = this.checkCompanyId(this.loginData.companyid);
if (CompanyId) {
this.loginProcess(this.loginData);
} else {
console.log('Company ID can't be found!')
}
}
},
}
}
</script>
I'm reconfiguring my NextJS/Apollo app to allow for SSG with GraphQL API routes, and I'm using this official NextJS starter example as a base for the client config.
I've run into an interesting issue though in my own app, so I've went back to starter example and tried to reproduce it, and was able to. The issue is that without any context object passed into the query resolvers, everything works fine (in the playground and on the client). However, when you introduce a context object and pass it to the resolvers, it works fine in the playground but the context object is undefined when fired from the client. This is the code from the official NextJS starter example, I'll comment where I've added anything.
graphql.js
import { ApolloServer } from "apollo-server-micro";
import { schema } from "../../apollo/schema";
const apolloServer = new ApolloServer({
schema,
context: { //
foo: "bar", // this is the context object I've added
}, //
});
export const config = {
api: {
bodyParser: false,
},
};
export default apolloServer.createHandler({ path: "/api/graphql" });
typedefs.js
import { gql } from '#apollo/client'
export const typeDefs = gql`
type User {
id: ID!
name: String!
status: String!
}
type Query {
viewer: User
}
`
schema.js
import { makeExecutableSchema } from 'graphql-tools'
import { typeDefs } from './type-defs'
import { resolvers } from './resolvers'
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
resolvers.js
export const resolvers = {
Query: {
viewer: (_parent, _args, context, _info) => {
console.log("context", context); // console log check that I've added
return { id: 1, name: "John Smith", status: "cached" };
},
},
};
When I run this in the GraphQL playground and query the API, it gives me the correct response, and in my terminal console it returns the context foo: bar object from the console log, so in the server the context object is being passed correctly. However, when I visit the index page in the browser, which is this:
index.js
import gql from "graphql-tag";
import Link from "next/link";
import { useQuery } from "#apollo/client";
import { initializeApollo } from "../apollo/client";
const ViewerQuery = gql`
query ViewerQuery {
viewer {
id
name
status
}
}
`;
const Index = () => {
const {
data: { viewer },
} = useQuery(ViewerQuery);
return (
<div>
You're signed in as {viewer.name} and you're {viewer.status} goto{" "}
<Link href="/about">
<a>static</a>
</Link>{" "}
page.
</div>
);
};
export async function getStaticProps() {
const apolloClient = initializeApollo();
await apolloClient.query({
query: ViewerQuery,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
},
};
}
export default Index;
...the viewer name and viewer status are rendered, so the query is actually happening, but in the console, the context object console log is returning undefined. So when used in the client, the context is being lost somehow. I find this interesting, since this is an official NextJS starter example, and unless they've set up the client to not accept context in the resolvers, I can't see what the problem is. And if it is the case that the client is not set up to accept context, is there any other official examples with a client setup that does?
This is a long question now, but here is the client.js setup:
import { useMemo } from "react";
import { ApolloClient, InMemoryCache } from "#apollo/client";
let apolloClient;
function createIsomorphLink() {
if (typeof window === "undefined") {
const { SchemaLink } = require("#apollo/client/link/schema");
const { schema } = require("./schema");
return new SchemaLink({ schema });
} else {
const { HttpLink } = require("#apollo/client/link/http");
return new HttpLink({
uri: "http://localhost:3000/api/graphql",
credentials: "same-origin",
});
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphLink(),
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
I implore anyone to clone this official repo and see if they can figure out how to get context working in the client, or if anyone knows why this client setup isn't working for context and knows a client setup that does accept resolver context, I would appreciate it. This problem has cost me two days now!
I've figured out the problem. The client configuration is using SchemaLink for the http request, and the context is passed in the SchemaLink constructor function, not in the server options, because context is passed in the http headers with httpLink.
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)
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": {
"#/*": ["./*"]
}
}
}