I am wrapping an older REST API service with an Apollo server. Calls to the REST service results in a JSON object that nests the payload 2 to 3 levels deep. For example:
{
- MRData: {
- CatTable : {
- Cats : []
And to further complicate matters, the nesting pattern and node names are different for each resource endpoint. So my question is, since each resource result will need custom manipulation, where is the best place to do it: in the Connector, Resolver or Model.
Connector
If done in the Connector, then a custom method is needed for each resource. Seems like a lot of boilerplate.
public fetchCats(resource: string) {
return new Promise<any>((resolve, reject) => {
request.get(url, (err, resp, body) => {
err ? reject(err) : resolve(JSON.parse(body).MRData.CatTable.Cats)
})
})
}
Resolver
The resolver method receives a promise but the result cannot be manipulated:
const allCats = (_, params, context) => context.cat.getCats()
.then((data) => { // to late to manipulate data here })
Model
The Model looks promising but not quite sure how to structure it:
public getCats() {
const cats = this.connector.fetchCats('/cats.json');
return cats;
}
Apollo will be (more often than not) integrated with REST API's. I'm looking forward discovering the best way to handle this case.
I would generally recommend doing the parsing in the connector, because they should abstract over the details of the backends. If connectors abstract over the backend, you should technically be able to switch out one backend for another when appropriate. For example you could switch from querying a REST API to sending queries directly to the database where it makes sense.
The consequence of this is that you'll need to build a new connector for every REST API, because no two REST APIs are the same.
Related
I have a specific use case where a user’s data sources are conditional - e.g based on the data sources saved in the database for every specific user.
This also means every data source has unique credentials for every user, which is fine for RESTDataSource because I can use the willSendRequest to set the Authentication headers before each request.
However, I have custom data sources that have proprietary clients (for example JSForce for Salesforce) - and they have their own fetch mechanism.
As of now - I have a custom transformer directive that fetches the tokens from the database and adds it into the context - however, the directive is ran before the dataSource.initialize() method - so that I can’t use the credentials there because the context still doesn’t have it.
I also don’t want to initialize all data sources for every user even if he doesn’t use said data source in this request - but the dataSources() function doesn’t accept any parameter and is not contextual.
Bottom line is - is it possible to pass data sources conditionally based even on the Express request? When is the right time to pass the tokens and credentials to the dataSource? Maybe add my own custom init function and call it from the directive?
So you have options. Here are 2 choices:
1. Just add your dataSources
If you just initialize all dataSources, internally it can check to see if the user has access. You could have a getClient function that resolves on the client or throws an UnauthorizedError, depending.
2. Don't just add your dataSources
So if you really don't want to initialize the dataSources at ALL, you can absolutely do this by adding the "dataSources" yourself, just like Apollo does it.
const server = new ApolloServer({
// this example uses apollo-server-express
context: async ({ req, res }) => {
const accessToken = req.headers?.authorization?.split(' ')[1] || ''
const user = accessToken && buildUser(accessToken)
const context = { user }
// You can't use the name "dataSources" in your config because ApolloServer will puke, so I called them "services"
await addServices(context)
return context
}
})
const addServices = async (context) => {
const { user } = context;
const services = {
userAPI: new UserAPI(),
postAPI: new PostAPI(),
}
if (user.isAdmin) {
services.adminAPI = new AdminAPI()
}
const initializers = [];
for (const service of Object.values(services)) {
if (service.initialize) {
initializers.push(
service.initialize({
context,
cache: null, // or add your own cache
})
);
}
}
await Promise.all(initializers);
/**
* this is where you have to deviate from Apollo.
* You can't use the name "dataSources" in your config because ApolloServer will puke
* with the error 'Please use the dataSources config option instead of putting dataSources on the context yourself.'
*/
context.services = services;
}
Some notes:
1. You can't call them "dataSources"
If you return a property called "dataSources" on your context object, Apollo will not like it very much [meaning it throws an Error]. In my example, I used the name "services", but you can do whatever you want... except "dataSources".
With the above code, in your resolvers, just reference context.services.whatever instead.
2. This is what Apollo does
This pattern is copied directly from what Apollo already does for dataSources [source]
3. I recommend you still treat them as DataSources
I recommend you stick to the DataSources pattern and that your "services" all extend DataSource. It's going to be easier for everyone involved.
4. Type safety
If you're using TypeScript or something, you're going to lose a bit of type safety, since the context.services is either going to be one shape or another. Even if you're not, if you're not careful, you may end up throwing "Cannot read property users of undefined" errors instead of "Unauthorized" errors. You might be better off creating "dummy services" that reflect the same object shape but just throw Unauthorized.
I created a GraphQL wrapper for PokeAPI. My queries all work in development fine and most of them work in production. However, I have the following query that works in production for smaller start and end ranges, but throws "ServerParseError: Unexpected token < in JSON at position 0" when I try to query for all of the pokemon with a very large range. This error does not happen in development.
query {
allPokemon(start: 0, end: 964) {
id
name
}
}
My resolver in my GraphQL for allPokemon only hits one REST endpoint and comes back with an array of objects that have the following structure:
{
name: "charmander",
url: "https://pokeapi.co/api/v2/pokemon/4/"
}
My resolver maps over the resulting array to grab the name value and to parse the url value to grab the id number at the end of the string.
Not sure if this is relevant/necessary to include here, but I am using apollo-datasource-rest. I created a class component that extends RESTDataSource that has abstracted out my functions for my GraphQL resolvers. Then I simply call those methods inside of my resolvers. My allPokemon method inside this RESTDataSource component looks like this:
async getAllPokemon(start = 0, end = 964) {
const response = await this.get(`pokemon?offset=${start}&limit=${end}`);
const pokemonIds = response.results.map(pokemon =>
parseUrl(pokemon.url)
);
return pokemonIds;
}
parseUrl is a utils function I created that just takes a url and parses it to grab the number at the end of the url after the last /.
Then in my resolvers, I have the following:
const resolvers = {
Query: {
allPokemon: (parent, args, { dataSources }) => {
return dataSources.pokemonAPI.getAllPokemon(args.start, args.end);
}
}
}
I can't seem to figure out if this is an issue with Heroku or with Apollo Server. My guess was with Heroku since I have no problems in development getting the expected data for all of the queries. I thought perhaps Heroku must have some limitations as far as timing out or how how many iterations of the parsing function it can do, but have been unable to confirm this theory, let alone find a solution. Any help is appreciated!
I have created an ASP.NET Core Web Api backend with an Angular 7 frontend. One of the methods in the Api can return either an object or an array to an Angular service. How do I route to specific observable, based on the data type returned? I am a noob to Angular, so any kind assistance would be appreciated.
Angular service call to Api:
getLinksFromSitus(situs: any) {
this.http.post(this.baseUrl + 'getLinksFromSitus', situs).subscribe(data =>
this.apiData.next(data)
);
}
Portion of Web Api that returns array if more than one APN present:
// if more than one item in list, get status information for each and return list to user to select appropriate apn
if (propApn.Count > 1)
{
return Ok(propApn);
}
Portion of same method to return object if only one value for APN:
var resultsModel = new Results
{
ArcGisLink = arcGisLink,
HistInfoLink = histInfoLink,
PropInfoLink = propInfoLink
};
return Ok(resultsModel);
You can't do this. Typescript can only type things based on static analysis at build time, what your describing would require Typescript to know the result of your API call at build time, which it doesn't do.
The best you can do is indicating that your API call can return both of your them:
public myApiFunc(req: MyRequestModel): Observable<any>
But that will still require you to figure out which type returned at runtime.
I was able to find a solution that worked...
getLinksFromSitus(situs: any) {
this.http.post(this.baseUrl + 'getLinksFromSitus', situs).subscribe(data => {
if (data.hasOwnProperty('arcGisLink')) {
this.apiData.next(data);
} else {
let vals = [];
vals = this.apiPropApn.getValue();
const item = vals.concat(data);
this.apiPropApn.next(item);
}
});
}
So, after subscribing to the HttpResponse, I am able to check if the data in the response contains a known property. If it doesn't contain the known property, then it concatenates the data to a BehaviorSubject array. It works perfectly.
I have a service that requests data from a get method, I'd like to map the response to an object storing some Ids and use those Ids to make other http requests.
I was told this isn't usually done in a callback manner, I looked at this How do I return the response from an asynchronous call? but I don't think it's the usual way to implement services, any hints are very appreciated.
Tried adding in onInit/constructor method in angular to be sure the object was filled before other methods were called without success.
#Injectable ()
export class ContactService {
storeIds;
getIds(callback: Function) {
this.http.get<any>(IdsUrl, Config.options).subscribe(res => {
callback(response);
});
getIds(res => {
this.storeIds = {
profileId: res.profile,
refIds: res.refIds
}
}
)
// this.storeIds returns undefined as it's an async call
this.http.post<any>(WebserviceUrl + this.storeIds.profileId , data, headers )
// .....Many other web services that relay on this Ids
}
Just create another service called StoreIdsService. Update the response you get from your first api call 'getIds' in the StoreIdsService. The idea is to have StoreIdsService as singleton service to keep state of your storeIds. You can inject StoreIdsService in anywhere component you want to get the storeIds.
Its one of manyways to share data in angular between components.
Please refer to this answer someone has posted.
How do I share data between components in Angular 2?
You can simply assign the service response to the storeIds property inside the subscribe method. and call the subsequent services inside it if you need.
#Injectable ()
export class ContactService {
storeIds;
getIds() {
this.http.get<any>(IdsUrl, Config.options).subscribe(res => {
this.storeIds = {
profileId: response.profile,
refIds: response.refIds
}
this.otherapicall1();
this.otherapicall2();
});
}
I have an endpoint that accepts as well as returns a reactive type. What I'm trying to achieve is to somehow verify that the complete reactive request (that is actually an array of resources) is valid before persisting the changes to the database (read Full-Update of a ressource). The question is not so much concerned with how to actually verify the request but more with how to chain the steps together using which of springs reactive handler methods (map, flatMap and the likes) in the desired order which is basically:
verify correctness of request (the Ressource is properly annotated with JSR-303 annotations)
clear the current resource in case of valid request
persist new resources in the database after clearing the database
Let's assume the following scenario:
val service : ResourceService
#PostMapping("/resource/")
fun replaceResources(#Valid #RequestBody resources:
Flux<RessourceDto>): Flux<RessourceDto> {
var deleteWrapper = Mono.fromCallable {
service.deleteAllRessources()
}
deleteWrapper = deleteWrapper.subscribeOn(Schedulers.elastic())
return deleteWrapper.thenMany<RessourceDto> {
resources
.map(mapper::map) // map to model object
.flatMap(service::createResource)
.map(mapper::map) // map to dto object
.subscribeOn(Schedulers.parallel())
}
}
//alternative try
#PostMapping("/resourceAlternative/")
override fun replaceResourcesAlternative2(#RequestBody resources:
Flux<ResourceDto>): Flux<ResourceDto> {
return service.deleteAllResources()
.thenMany<ResourceDto> {
resources
.map(mapper::map)
.flatMap(service::createResource)
.map(mapper::map)
}
}
Whats the idiomatic way of doing this in a reactive fashion?