An Apollo data source for Azure CosmosDB
npm install apollo-datasource-cosmosdbThis is a CosmosDB DataSource for the Apollo GraphQL Server. It was adapted from the MongoDB Data Source project.
Use by creating a new class, inheriting from CosmosDataSource passing in the CosmosDb container instance (created from the CosmosDB Javascript API). Use a separate DataSource for each data type.
Example:
data-sources/Users.ts
``typescript
export interface UserDoc {
id: string; // a string id value is required for entities using this library
name: string;
}
export class UserDataSource extends CosmosDataSource
export class PostDataSource extends CosmosDataSource
`
server.ts
`typescript
import { CosmosClient } from "@azure/cosmos";
import { CosmosDataSource } from "apollo-datasource-cosmosdb";
const cosmosClient = new CosmosClient({
endpoint: "https://my-cosmos-db.documents.azure.com:443/",
key: "--------key-goes-here---==",
});
const cosmosContainer = cosmosClient.database("MyDatabase").container("Items");
import UserDataSource from "./data-sources/Users.js";
import PostDataSource from "./data-sources/Users.js";
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => ({
users: new UserDataSource(cosmosContainer),
posts: new PostDataSource(cosmosContainer),
}),
});
`
It is often useful to define a context. See Apollo docs on context To make this strongly typed, there is a second type paramater on the CosmosDbDataSource:
`typescript
interface MyQueryContext {
currentUserId: string
}
/////
const userDataSource extends CosmosDataSource
`
CosmosDataSource exposes a findManyByQuery method that accepts a ComosDB SQL query either as a string or a SqlQuerySpec object containing the query and a parameter collection. This can be used directly in the resolvers, but probably better to create wrappers that hide the query details.
Creating a derived class with custom query methods, you can hide all of your query logic in the DataSource class:
`typescriptUserDataSource: getGroupUsers ${group_id}
export class UserDataSource extends CosmosDataSource
findManyByGroupID = async (group_id, args: FindArgs = {}) => {
log.debug();SELECT FROM c where c.type = "User" and exists(select from g in c.groups where g = @group_id)
const query = ;Result count ${results.resources.length}
const results = await this.findManyByQuery(
{
query,
parameters: [{ name: "@group_id", value: group_id }],
},
args
);
log.debug();SELECT * FROM c WHERE c.userName = @userName AND c.type = 'User'
return results.resources;
};
findOneByUserName = async (userName: string, args: FindArgs = {}) => {
const results = await this.findManyByQuery(
{
query: ,`
parameters: [{ name: "@userName", value: userName }],
},
args
);
if (results.resources && results.resources.length)
return results.resources[0];
return undefined;
};
}
This DataSource has some built in mutation methods to create, update and delete items. They can be used directly in resolvers or wrapped with custom methods.
`typescript
await context.dataSources.users.createOne(userDoc);
await context.dataSources.users.updateOne(userDoc);
await context.dataSources.users.updateOnePartial(user_id, { name: "Bob" });
await context.dataSources.users.deleteOne(userId);
`
The data loader (and cache, if used) are updated after mutation operations.
Batching is provided on all queries using the DataLoader library.
Caching is available on an opt-in basis by passing a ttl option on queries.
This library is written in Typescript and exports full type definitions, but usable in pure Javascript as well. This works really well with GraphQL Codegen's typed resolvers.
`typescript
const thisUser = await users.findOneById(id: string, {ttl}) // => Promise
const userPair = await users.findManyByIds([id1, id2], {ttl}) // => Promise<(T | undefined)[]>
await users.deleteFromCacheById(id}) // => Promise
// query method; note that this returns the CosmosDB FeedResponse object because sometimes this extra information is useful
const userListResponse = await users.findManyByQuery('SELECT * from c where c.type="User"', {ttl, requestOptions}) // => Promise
console.log(userListResponse.resources) // user array from query
// create method returns the CosmosDB ItemResponse object
const createResponse = await users.createOne(newUser) // => Promise
console.log(createResponse.resource) // user object returned from create, with CosmosDB-added values
const updateUserResponse = await users.updateOne(thisUser) // => Promise
const updatePartialResponse = await users.updateOnePartial(id, {firstName: "Bob"}) // => Promise
console.log(updatePartialResponse.resource) // full user object from DB after updates
``