Say I have an 2 API classes that extend RESTDataSource:
class MoviesAPI extends RESTDataSource {
async getMovies() {}
}
class SongsAPI extends RESTDataSource {
async getSongs() {}
}
How can I call getSongs from getMovies within the existing Apollo server context?
You can get the apollo server context via this.context in the datasource class and get dataSources via this.context.dataSources.
E.g.
server.ts:
import { ApolloServer, gql } from 'apollo-server';
import { MoviesAPI } from './MoviesAPI';
import { SongsAPI } from './SongsAPI';
const typeDefs = gql`
type Query {
movies: String
}
`;
const resolvers = {
Query: {
movies: (_, __, { dataSources }) => {
return dataSources.moviesAPI.getMovies();
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => {
return {
moviesAPI: new MoviesAPI(),
songsAPI: new SongsAPI(),
};
},
});
server.listen().then(({ url }) => {
console.log(`Apollo server is listening on ${url}`);
});
MoviesAPI.ts:
import { RESTDataSource } from 'apollo-datasource-rest';
export class MoviesAPI extends RESTDataSource {
async getMovies() {
const songs = await this.context.dataSources.songsAPI.getSongs();
const movies = ['a', 'b'];
return JSON.stringify({ movies, songs });
}
}
SongsAPI.ts:
import { RESTDataSource } from 'apollo-datasource-rest';
export class SongsAPI extends RESTDataSource {
async getSongs() {
return ['x', 'y'];
}
}
Send a GraphQL query from the client-side:
query{
movies
}
Response payload:
{
"data": {
"movies": "{\"movies\":[\"a\",\"b\"],\"songs\":[\"x\",\"y\"]}"
}
}
Package versions: "apollo-datasource-rest": "^0.8.1", "apollo-server": "^2.12.0"
source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/61425326
Related
I can't figure out how to access the query for a element through code.
I have query request, but I do now know how to fetch and map the query to elements.
Here is the code of query fetch:
import { request, gql } from "graphql-request";
const graphqlAPI = process.env.NEXT_PUBLIC_GRAPHCMS_ENDPOINT;
export const getPosts = async () =\> {
const query = gql` query MyQuery { postsConnection { edges { node { author { bio name id photo { url } } createdAt slug title excerpt featuredImage { url } categories { name slug } } } } } `;
const results = await request(graphqlAPI, query);
return results.postConnection.edges;
};
Here is where I specify the endpoint:
NEXT_PUBLIC_GRAPHCMS_ENDPOINT=https://api-eu-central-1-shared-euc1-02.hygraph.com/v2/cl9pn1q9a1ubu01t243uj1rnr/master
Here is how I imagine doing this but can't access the proper properties/queries:
import { getPosts } from "../services/GraphRequests";
import React from "react";
function Blog() {
return <div>{getPosts}</div>;
or
return <div>getPosts.map or something)
}
export default Blog;
Here is how I tried to get the data:
import React from "react";
import { useEffect, useState } from "react";
import request from "graphql-request";
function Blog() {
const [posts, setPosts] = useState(null);
useEffect(() => {
const fetchPosts = async () => {
const { posts } = await request(
"https://api-eu-central-1-shared-euc1-02.hygraph.com/v2/cl9pn1q9a1ubu01t243uj1rnr/master",
`{postsConnection {
edges {
node {
author {
bio
name
id
photo {
url
}
}
createdAt
slug
title
excerpt
featuredImage {
url
}
categories {
name
slug
}
}
}
}}`
);
setPosts(posts);
};
fetchPosts();
}, []);
return <div>{console.log(posts)}</div>;
}
export default Blog;
This is how I made it work:
import React from "react";
import request, { gql } from "graphql-request";
function Blog() {
const QUERY = gql`
query MyQuery {
postsConnection {
edges {
node {
author {
bio
name
id
photo {
url
}
}
createdAt
slug
title
excerpt
featuredImage {
url
}
categories {
name
slug
}
}
}
}
}
`;
const theFetchedData = request(
"https://api-eu-central-1-shared-euc1-02.hygraph.com/v2/cl9pn1q9a1ubu01t243uj1rnr/master",
QUERY
).then((data) => console.log(data));
return <div>{JSON.stringify(theFetchedData)}</div>;
}
export default Blog;
I'm trying to create a base class that contains common logic between multiple AWS Lambdas. However, my test fails with:
Class extends value undefined is not a constructor or null
I tried to create a base handler like this:
import { APIGatewayEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { Logger } from '..';
export abstract class BaseHandler<T extends APIGatewayEvent> {
abstract process(event: T, context: Context): Promise<APIGatewayProxyResult>;
public handle = async (event: T, context: Context): Promise<APIGatewayProxyResult> => {
Logger.debug(`Received event: ${JSON.stringify(event)}`);
Logger.debug(`Received context: ${JSON.stringify(context)}`);
try {
const result: APIGatewayProxyResult = await this.process(event, context);
Logger.debug(`Returning response: ${JSON.stringify(result)}`);
return result;
} catch (e) {
Logger.error(`Caught error in handler: ${JSON.stringify(e)}`);
const errorResponse: APIGatewayProxyResult = {
statusCode: statusCode,
body: JSON.stringify({
code: e.statusCode,
message: e.message
})
};
return errorResponse;
}
};
}
module.exports.BaseHandler;
And then a specific lambda that inherits this:
import { BaseHandler } from '../base-handler.abstract.class';
import { APIGatewayProxyResult, Context } from 'aws-lambda';
import { InheritedLambdaRequest } from '../types/inherited-lambda-request.type';
class InheritedLambda extends BaseHandler<InheritedLambdaRequest> {
constructor() {
super();
}
public process = async (event: InheritedLambdaRequest, context: Context): Promise<APIGatewayProxyResult> => {
//business logic
}
}
export const handler = new InheritedLambda();
export const inheritedLambda = handler.process;
However, my jest test fails:
import { inheritedLambda } from './inherited-lambda';
describe('Handle Inherited Lambda', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should return 204', (done) => {
inheritedLambda(request, undefined).then((result) => {
expect(result.statusCode).toEqual(204);
expect(result.body).toEqual('');
done();
});
});
});
I'm trying to use repository pattern with TypeScript
Now I have base.repository that implements all of the functions that I need, I made it a generic type, and I wanna pass the model while injecting it in constructor, but for some reason, while passing the value, I have undefined state of the particular model, what am I doing wrong?
In the console.log() it shows me that the model is undefined while in file register.service.ts it shows me also undefined, but I passed it as generic.
register.service.ts
import { BaseRepository } from "../repositories/base.repository";
import { Creator } from '../data/models/Creator'
import { RegisterDto } from "../types/dtos/register.dto";
import { Injectable } from '#nestjs/common'
import { RegisterMapper } from '../mappers/register.mapper'
import { errors } from '../errors'
import { mailer } from '../utils/nodemailer'
#Injectable()
export class RegisterService {
constructor(
private readonly repository: BaseRepository<Creator>,
private readonly mapper: RegisterMapper
) { }
async createAccount (doc: RegisterDto) {
const emailExist = await this.existByEmail(doc.email)
if (emailExist) {
return errors.EMAIL_EXIST()
}
const created = await this.repository.create(this.mapper.toDomain(doc))
await mailer(doc.email)
return created
}
private async existByEmail(email: string): Promise<boolean> {
console.log(email)
console.log(this.repository)
const response = await this.repository.get({ email })
return !!response.email;
}
}
base.repository.ts
import { ModelType } from '#typegoose/typegoose/lib/types'
import { DuplicateKeyError } from '../errors/DuplicateKeyError'
import { DocumentNotFoundError } from '../errors/DocumentNotFoundError'
import { Model } from 'mongoose'
import { Inject, Injectable, Optional } from '#nestjs/common'
#Injectable()
export class BaseRepository<T = any> {
constructor(
#Optional() #Inject('MODEL') private Model: any
) { }
async create (object): Promise<T> {
const Model = this.Model
console.log(Model)
const uniqueKey = Model.getUniqueKey ? Model.getUniqueKey() : null
if (uniqueKey && object[uniqueKey]) {
const criteria = {
[uniqueKey]: object[uniqueKey]
}
const existing = await Model.findOne(criteria)
if (existing) {
throw new DuplicateKeyError(Model, criteria)
}
}
const model = new Model(object)
return model.save()
}
async update (criteria, object, options = {}) {
const Model = this.Model
const uniqueKey = Model.getUniqueKey ? Model.getUniqueKey() : '_id'
const data = { ...object }
delete data[uniqueKey]
delete data.createdAt
return this.updateRaw(criteria, { $set: { ...data } }, options)
}
async updateRaw (criteria, data, options = {}) {
const query = this._getDbQuery(criteria, options)
const result = await this.Model.findOneAndUpdate(query, data, { new: true, ...options })
if (!result) {
throw new DocumentNotFoundError(this.Model, query)
}
return result
}
async save (modelInstance) {
return modelInstance.save()
}
async get (criteria, options: any = {}): Promise<T | undefined> {
console.log(Model)
const promise = await this.Model.findOne(this._getDbQuery(criteria, options)).exec()
if (options.select) {
promise.select(options.select)
}
return promise
}
async find (criteria, options): Promise<ReturnType<ModelType<T>['find']>> {
return this.Model.find(this._getDbQuery(criteria, options))
}
async resolve (criteria): Promise<T> {
return this.Model.resolve(this._getDbQuery(criteria))
}
async count (query) {
return this.Model.countDocuments(this._getDbQuery(query))
}
async delete (criteria) {
return this.Model.remove(this._getDbQuery(criteria))
}
_getDbQuery (criteria, options: any = {}) {
if ('getDbQuery' in criteria) {
const dbQuery = criteria.getDbQuery(options)
return 'find' in dbQuery
? dbQuery.find
: dbQuery
} else {
return criteria
}
}
}
What should I do to get the actual model in this repository?
I have added inject tokens in each service before repository Injection
Now the code looks as follows
import { BaseRepository } from "../repositories/base.repository";
import { Creator } from '../data/models/Creator'
import { RegisterDto } from "../types/dtos/register.dto";
import { Injectable } from '#nestjs/common'
import { RegisterMapper } from '../mappers/register.mapper'
import { errors } from '../errors'
import { mailer } from '../utils/nodemailer'
import { CREATOR } from '../utils'
#Injectable()
export class RegisterService {
registerMapper = new RegisterMapper()
constructor(
#Inject(CREATOR) private readonly repository: BaseRepository<Creator>,
) { }
async createAccount (doc: RegisterDto) {
const emailExist = await this.existByEmail(doc.email)
if (emailExist) {
return errors.EMAIL_EXIST()
}
const created = await this.repository.create(this.mapper.toDomain(doc))
await mailer(doc.email)
return created
}
private async existByEmail(email: string): Promise<boolean> {
console.log(email)
console.log(this.repository)
const response = await this.repository.get({ email })
return !!response.email;
}
}
inject-tokens.ts
export const CREATOR = 'CREATOR'
created state to store the ipaddress
auth.js
const state = {ipAddress: ''}
const getters = {ipAddress: (state) => {
return state.ipAddress
}
}
const actions = {
async getIpAddress ({commit}) {
const { data: { ip } } = await axios.get("https://www.cloudflare.com/cdn-cgi/trace", {responseType: "text", transformResponse: data =>
Object.fromEntries(data.trim().split("\n").map(line => line.split("=")))
});
commit('setIp', ip)},
}
here is the ip address
const mutations = {
setIp(state, payload) {
state.ipAddress = payload
},}
App.vue
Calling the fucntion ...
<script>
import { mapActions, mapState, mapGetters } from "vuex";
Vue.mixin(windowMixin)
export default {
name: "App",
methods: {
...mapActions('auth',['getIpAddress']),
},
mounted() {
this.getIpAddress();
},
};
</script>
product.service.js
sending the ip as a parameters
import ApiService from "../api.service";
const ProductService = {
async productDetails(productID,ipAddress){
const requestData = {
method: 'get',
url: `/api/products/v1/product/detail/?itemId=${productID}`,
params: {
detailToken: localStorage.getItem('detailData') && localStorage.getItem('detailData'),
ip: ipAddress
}
}
Here I added this ipAddress to productDetails in another Component,
but I want to import it in product.service.js directly, Please help me !
I have created a React App and I am using .Net Core in the backend, the list of data from backend is successfully received, but in react while using Map it only shows one item from the list.I ma using MObX for state management.
My Code is :
import React, { useContext, useEffect } from 'react'
import { RootStoreContext } from '../../app/stores/rootStore';
import { observer } from 'mobx-react-lite';
import { Segment, Item, Icon, Button } from 'semantic-ui-react';
import { format } from 'date-fns';
import { Link } from 'react-router-dom';
const BookList: React.FC = () => {
const rootStore = useContext(RootStoreContext);
const { loadBooks, getAvailableBooks } = rootStore.bookStore;
useEffect(() => {
loadBooks();
}, [loadBooks]);
return (
<div>
{getAvailableBooks.map(books => (
<Segment.Group key={books.bookName}>
<Segment>
<Item.Group>
<Item>
<Item.Image size='tiny' circular src='/assets/user.png' />
<Item.Content>
<Item.Header as='a'>{books.bookName}</Item.Header>
</Item.Content>
</Item>
</Item.Group>
</Segment>
</Segment.Group>
))}
</div>
)
}
export default observer(BookList);
My BookStore is :
import { observable, action, computed, runInAction } from "mobx";
import agent from "../api/agent";
import { RootStore } from "./rootStore";
import { IBooks } from "../models/books";
export default class BookStore {
rootStore: RootStore;
constructor(rootStore: RootStore) {
this.rootStore = rootStore;
}
#observable bookRegistry = new Map();
#observable book: IBooks | null = null;
#observable loadingInitial = false;
#computed get getAvailableBooks() {
return Array.from(this.bookRegistry.values());
}
#action loadBooks = async () => {
this.loadingInitial = true;
try {
const books = await agent.Books.list();
runInAction("loading books", () => {
books.forEach((books) => {
books.issuedOn = new Date(books.issuedOn);
this.bookRegistry.set(books.id, books);
});
this.loadingInitial = false;
});
} catch (error) {
runInAction("load books error", () => {
this.loadingInitial = false;
});
}
};
}
and API is called from agent.ts
import axios, { AxiosResponse } from "axios";
import { history } from "../..";
import { toast } from "react-toastify";
import { IBooks } from "../models/books";
axios.defaults.baseURL = "https://localhost:44396/api";
const requests = {
get: (url: string) => axios.get(url).then(sleep(1000)).then(responseBody),
post: (url: string, body: {}) =>
axios.post(url, body).then(sleep(1000)).then(responseBody),
put: (url: string, body: {}) =>
axios.put(url, body).then(sleep(1000)).then(responseBody),
del: (url: string) => axios.delete(url).then(sleep(1000)).then(responseBody),
};
const Books = {
list: (): Promise<IBooks[]> => requests.get("/Book/GetBookList"),
};
export default {
User
};
export interface IBooks {
id: number;
bookname: string;
issuedOn: Date;
isReturned: boolean;
isRequested: boolean;
isAvailable: boolean;
isTaken: boolean;
name: string;
}
The response from API
from the screenshot of your API response, it seems that each "book" object does not have an id property. This might explain why you only see one element rendered, because in your loadBooks action, each time you try to do this.bookRegistry.set(books.id, books), you're using undefined as the key, and then on the next iteration you overwrite the value stored at that key.