Skip to content

Commit

Permalink
docs(examples): add Apollo Federation v2 example (#1448)
Browse files Browse the repository at this point in the history
Co-authored-by: Michał Lytek <michal.wojciech.lytek@gmail.com>
  • Loading branch information
jhanggi and MichalLytek committed Oct 4, 2023
1 parent 615680e commit 9841e0f
Show file tree
Hide file tree
Showing 33 changed files with 615 additions and 2 deletions.
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"options": [
"apollo-cache",
"apollo-federation",
"apollo-federation-2",
"authorization",
"automatic-validation",
"custom-validation",
Expand Down
3 changes: 2 additions & 1 deletion docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ Each subdirectory contains a `examples.graphql` file with predefined GraphQL que
- [TypeORM (automatic, lazy relations) \*](https://github.com/MichalLytek/type-graphql/tree/master/examples/typeorm-lazy-relations)
- [MikroORM \*](https://github.com/MichalLytek/type-graphql/tree/master/examples/mikro-orm)
- [Typegoose \*](https://github.com/MichalLytek/type-graphql/tree/master/examples/typegoose)
- [Apollo federation](https://github.com/MichalLytek/type-graphql/tree/master/examples/apollo-federation)
- [Apollo Federation](https://github.com/MichalLytek/type-graphql/tree/master/examples/apollo-federation)
- [Apollo Federation 2](https://github.com/MichalLytek/type-graphql/tree/master/examples/apollo-federation-2)
- [Apollo Cache Control](https://github.com/MichalLytek/type-graphql/tree/master/examples/apollo-cache)
- [GraphQL Scalars](https://github.com/MichalLytek/type-graphql/tree/master/examples/graphql-scalars)
- [TSyringe](https://github.com/MichalLytek/type-graphql/tree/master/examples/tsyringe)
Expand Down
3 changes: 2 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ Each subdirectory contains a `examples.graphql` file with predefined GraphQL que
- [TypeORM (automatic, lazy relations) \*](./typeorm-lazy-relations)
- [MikroORM \*](./mikro-orm)
- [Typegoose \*](./typegoose)
- [Apollo federation](./apollo-federation)
- [Apollo Federation](./apollo-federation)
- [Apollo Federation 2](./apollo-federation-2)
- [Apollo Cache Control](./apollo-cache)
- [GraphQL Scalars](./graphql-scalars)
- [TSyringe](./tsyringe)
Expand Down
20 changes: 20 additions & 0 deletions examples/apollo-federation-2/accounts/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { User } from "./user";

function createUser(userData: Partial<User>) {
return Object.assign(new User(), userData);
}

export const users: User[] = [
createUser({
id: "1",
name: "Ada Lovelace",
birthDate: "1815-12-10",
username: "@ada",
}),
createUser({
id: "2",
name: "Alan Turing",
birthDate: "1912-06-23",
username: "@complete",
}),
];
25 changes: 25 additions & 0 deletions examples/apollo-federation-2/accounts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { AccountsResolver } from "./resolver";
import { User } from "./user";
import { resolveUserReference } from "./user.reference";
import { buildFederatedSchema } from "../helpers/buildFederatedSchema";

export async function listen(port: number): Promise<string> {
const schema = await buildFederatedSchema(
{
resolvers: [AccountsResolver],
orphanedTypes: [User],
},
{
User: { __resolveReference: resolveUserReference },
},
);

const server = new ApolloServer({ schema });

const { url } = await startStandaloneServer(server, { listen: { port } });
console.log(`Accounts service ready at ${url}`);

return url;
}
11 changes: 11 additions & 0 deletions examples/apollo-federation-2/accounts/resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Query, Resolver } from "type-graphql";
import { users } from "./data";
import { User } from "./user";

@Resolver(_of => User)
export class AccountsResolver {
@Query(_returns => User)
me(): User {
return users[0];
}
}
6 changes: 6 additions & 0 deletions examples/apollo-federation-2/accounts/user.reference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { users } from "./data";
import { type User } from "./user";

export async function resolveUserReference(reference: Pick<User, "id">): Promise<User> {
return users.find(u => u.id === reference.id)!;
}
18 changes: 18 additions & 0 deletions examples/apollo-federation-2/accounts/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Directive, Field, ID, ObjectType } from "type-graphql";

@Directive(`@key(fields: "id")`)
@ObjectType()
export class User {
@Field(_type => ID)
id!: string;

@Directive("@shareable")
@Field()
username!: string;

@Field()
name!: string;

@Field()
birthDate!: string;
}
23 changes: 23 additions & 0 deletions examples/apollo-federation-2/examples.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
query {
topProducts {
__typename
name
price
shippingEstimate
inStock
reviews {
body
author {
name
birthDate
reviews {
product {
__typename
name
}
body
}
}
}
}
}
40 changes: 40 additions & 0 deletions examples/apollo-federation-2/helpers/buildFederatedSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { buildSubgraphSchema } from "@apollo/subgraph";
import { type IResolvers, printSchemaWithDirectives } from "@graphql-tools/utils";
import gql from "graphql-tag";
import deepMerge from "lodash.merge";
import { type BuildSchemaOptions, buildSchema, createResolversMap } from "type-graphql";

export async function buildFederatedSchema(
options: Omit<BuildSchemaOptions, "skipCheck">,
referenceResolvers?: IResolvers,
) {
// build TypeGraphQL schema
const schema = await buildSchema({
...options,
skipCheck: true, // disable check to allow schemas without query, etc.
});

// build Apollo Subgraph schema
const federatedSchema = buildSubgraphSchema({
typeDefs: gql`
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: [
"@key"
"@shareable"
"@provides"
"@extends"
"@requires"
"@external"
"@interfaceObject"
]
)
${printSchemaWithDirectives(schema)}
`,
// merge schema's resolvers with reference resolvers
resolvers: deepMerge(createResolversMap(schema) as any, referenceResolvers),
});

return federatedSchema;
}
46 changes: 46 additions & 0 deletions examples/apollo-federation-2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import "reflect-metadata";
import path from "path";
import { ApolloGateway, IntrospectAndCompose } from "@apollo/gateway";
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { emitSchemaDefinitionFile } from "type-graphql";
import * as accounts from "./accounts";
import * as inventory from "./inventory";
import * as products from "./products";
import * as reviews from "./reviews";

const startGraph = async (name: string, urlOrPromise: string | Promise<string>) => {
const url = await urlOrPromise;
return { name, url };
};

async function bootstrap() {
const subgraphs = await Promise.all([
startGraph("accounts", accounts.listen(4001)),
startGraph("reviews", reviews.listen(4002)),
startGraph("products", products.listen(4003)),
startGraph("inventory", inventory.listen(4004)),
]);

const schemaGateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs,
}),
});
const { schema } = await schemaGateway.load();
await emitSchemaDefinitionFile(path.resolve(__dirname, "schema.graphql"), schema);
await schemaGateway.stop();

const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs,
}),
});
const server = new ApolloServer({ gateway });

const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });

console.log(`Apollo Gateway ready at ${url}`);
}

bootstrap().catch(console.error);
10 changes: 10 additions & 0 deletions examples/apollo-federation-2/inventory/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface Inventory {
upc: string;
inStock: boolean;
}

export const inventory: Inventory[] = [
{ upc: "1", inStock: true },
{ upc: "2", inStock: false },
{ upc: "3", inStock: true },
];
25 changes: 25 additions & 0 deletions examples/apollo-federation-2/inventory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { Product } from "./product";
import { resolveProductReference } from "./product.reference";
import { InventoryResolver } from "./resolver";
import { buildFederatedSchema } from "../helpers/buildFederatedSchema";

export async function listen(port: number): Promise<string> {
const schema = await buildFederatedSchema(
{
resolvers: [InventoryResolver],
orphanedTypes: [Product],
},
{
Product: { __resolveReference: resolveProductReference },
},
);

const server = new ApolloServer({ schema });

const { url } = await startStandaloneServer(server, { listen: { port } });
console.log(`Inventory service ready at ${url}`);

return url;
}
17 changes: 17 additions & 0 deletions examples/apollo-federation-2/inventory/product.reference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { inventory } from "./data";
import { Product } from "./product";

export async function resolveProductReference(
reference: Pick<Product, "upc">,
): Promise<Product | undefined> {
const found = inventory.find(i => i.upc === reference.upc);

if (!found) {
return undefined;
}

return Object.assign(new Product(), {
...reference,
...found,
});
}
22 changes: 22 additions & 0 deletions examples/apollo-federation-2/inventory/product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Directive, Field, ObjectType } from "type-graphql";

@ObjectType()
@Directive("@extends")
@Directive("@interfaceObject")
@Directive(`@key(fields: "upc")`)
export class Product {
@Field()
@Directive("@external")
upc!: string;

@Field()
@Directive("@external")
weight!: number;

@Field()
@Directive("@external")
price!: number;

@Field()
inStock!: boolean;
}
17 changes: 17 additions & 0 deletions examples/apollo-federation-2/inventory/resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Directive, FieldResolver, Resolver, Root } from "type-graphql";
import { Product } from "./product";

@Resolver(_of => Product)
export class InventoryResolver {
@Directive(`@requires(fields: "price weight")`)
@FieldResolver(_returns => Number)
async shippingEstimate(@Root() product: Product): Promise<number> {
// free for expensive items
if (product.price > 1000) {
return 0;
}

// estimate is based on weight
return product.weight * 0.5;
}
}
27 changes: 27 additions & 0 deletions examples/apollo-federation-2/products/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Dining } from "./dining";
import { type Product } from "./product";
import { Seating } from "./seating";

export const products: Product[] = [
Object.assign(new Dining(), {
upc: "1",
name: "Table",
price: 899,
weight: 100,
height: "3ft",
}),
Object.assign(new Seating(), {
upc: "2",
name: "Couch",
price: 1299,
weight: 1000,
seats: 2,
}),
Object.assign(new Seating(), {
upc: "3",
name: "Chair",
price: 54,
weight: 50,
seats: 1,
}),
];
9 changes: 9 additions & 0 deletions examples/apollo-federation-2/products/dining.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Directive, Field, ObjectType } from "type-graphql";
import { Product } from "./product";

@Directive(`@key(fields: "upc")`)
@ObjectType({ implements: Product })
export class Dining extends Product {
@Field()
height!: string;
}
25 changes: 25 additions & 0 deletions examples/apollo-federation-2/products/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { Product } from "./product";
import { resolveProductReference } from "./product.reference";
import { ProductsResolver } from "./resolver";
import { buildFederatedSchema } from "../helpers/buildFederatedSchema";

export async function listen(port: number): Promise<string> {
const schema = await buildFederatedSchema(
{
resolvers: [ProductsResolver],
orphanedTypes: [Product],
},
{
Product: { __resolveReference: resolveProductReference },
},
);

const server = new ApolloServer({ schema });

const { url } = await startStandaloneServer(server, { listen: { port } });
console.log(`Products service ready at ${url}`);

return url;
}
8 changes: 8 additions & 0 deletions examples/apollo-federation-2/products/product.reference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { products } from "./data";
import { type Product } from "./product";

export async function resolveProductReference(
reference: Pick<Product, "upc">,
): Promise<Product | undefined> {
return products.find(p => p.upc === reference.upc);
}
Loading

0 comments on commit 9841e0f

Please sign in to comment.