Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion examples/graphql/src/__tests__/acceptance/graphql-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,13 @@ mutation AddRecipe {
numberInCollection
creationDate
}
}`;
}

subscription AllNotifications {
recipeCreated {
id
numberInCollection
creationDate
}
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ describe('GraphQL server', () => {
runTests(() => supertest(server.httpServer?.url));

async function givenServer() {
server = new GraphQLServer({host: '127.0.0.1', port: 0});
server = new GraphQLServer({
host: '127.0.0.1',
port: 0,
apollo: {subscriptions: '/subscriptions'},
});
server.resolver(RecipeResolver);

server.bind('recipes').to([...sampleRecipes]);
Expand Down
3 changes: 0 additions & 3 deletions examples/graphql/src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ export class GraphqlDemoApplication extends BootMixin(
this.component(GraphQLComponent);
const server = this.getSync(GraphQLBindings.GRAPHQL_SERVER);
this.expressMiddleware('middleware.express.GraphQL', server.expressApp);
this.configure(GraphQLBindings.GRAPHQL_SERVER).to({
asMiddlewareOnly: true,
});

// It's possible to register a graphql context resolver
this.bind(GraphQLBindings.GRAPHQL_CONTEXT_RESOLVER).to(context => {
Expand Down
17 changes: 15 additions & 2 deletions examples/graphql/src/graphql-resolvers/recipe-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ import {
GraphQLBindings,
Int,
mutation,
Publisher,
pubSub,
query,
resolver,
ResolverData,
ResolverInterface,
root,
subscription,
} from '@loopback/graphql';
import {repository} from '@loopback/repository';
import {RecipeInput} from '../graphql-types/recipe-input';
Expand Down Expand Up @@ -46,8 +49,18 @@ export class RecipeResolver implements ResolverInterface<Recipe> {
}

@mutation(returns => Recipe)
async addRecipe(@arg('recipe') recipe: RecipeInput): Promise<Recipe> {
return this.recipeRepo.add(recipe);
async addRecipe(
@arg('recipe') recipe: RecipeInput,
@pubSub('recipeCreated') publish: Publisher<Recipe>,
): Promise<Recipe> {
const result = await this.recipeRepo.add(recipe);
await publish(result);
return result;
}

@subscription(returns => Recipe, {topics: 'recipeCreated'})
async recipeCreated(@root() recipe: Recipe) {
return recipe;
}

@fieldResolver()
Expand Down
9 changes: 8 additions & 1 deletion examples/graphql/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {GraphQLServerOptions} from '@loopback/graphql';
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/example-graphql
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {ApplicationConfig, GraphqlDemoApplication} from './application';

export * from './application';
Expand All @@ -20,6 +20,12 @@ export async function main(options: ApplicationConfig = {}) {
}

if (require.main === module) {
const graphqlCfg: GraphQLServerOptions = {
apollo: {
subscriptions: '/subscriptions',
},
asMiddlewareOnly: true,
};
// Run the application
const config = {
rest: {
Expand All @@ -36,6 +42,7 @@ if (require.main === module) {
setServersFromRequest: true,
},
},
graphql: graphqlCfg,
};
main(config).catch(err => {
console.error('Cannot start the application.', err);
Expand Down
15 changes: 13 additions & 2 deletions extensions/graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ export class GraphqlDemoApplication extends BootMixin(
super(options);

this.component(GraphQLComponent);
const server = this.getSync(GraphQLBindings.GRAPHQL_SERVER);
this.expressMiddleware('middleware.express.GraphQL', server.expressApp);
this.configure(GraphQLBindings.GRAPHQL_SERVER).to({
asMiddlewareOnly: true,
});
const server = this.getSync(GraphQLBindings.GRAPHQL_SERVER);
this.expressMiddleware('middleware.express.GraphQL', server.expressApp);

// ...

Expand All @@ -89,6 +89,17 @@ export class GraphqlDemoApplication extends BootMixin(
}
```

The GraphQLServer configuration can also be passed in from the application
config, such as:

```ts
const app = new Application({
graphql: {
asMiddlewareOnly: true,
},
});
```

## Add GraphQL types

The `@loopback/graphql` packages supports GraphQL schemas to be defined using
Expand Down
30 changes: 27 additions & 3 deletions extensions/graphql/src/__tests__/unit/graphql.component.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,38 @@

import {Application} from '@loopback/core';
import {expect} from '@loopback/testlab';
import {GraphQLComponent} from '../../graphql.component';
import {GraphQLBindings} from '../../keys';
import {GraphQLBindings, GraphQLComponent} from '../..';

describe('GraphQL component', () => {
it('binds server and booter bindings;', () => {
it('binds server and booter bindings', () => {
const app = new Application();
app.component(GraphQLComponent);
expect(app.isBound(GraphQLBindings.COMPONENT)).to.be.true();
expect(app.isBound('booters.GraphQLResolverBooter')).to.be.true();
});

it('configures GraphQL server from app config', () => {
const app = new Application({
graphql: {
asMiddlewareOnly: true,
},
});
app.component(GraphQLComponent);
expect(app.getConfigSync(GraphQLBindings.GRAPHQL_SERVER)).to.eql({
asMiddlewareOnly: true,
});
});

it('configures GraphQL server to override app config', () => {
const app = new Application({
graphql: {
asMiddlewareOnly: true,
},
});
app.component(GraphQLComponent);
app.configure(GraphQLBindings.GRAPHQL_SERVER).to({asMiddlewareOnly: false});
expect(app.getConfigSync(GraphQLBindings.GRAPHQL_SERVER)).to.eql({
asMiddlewareOnly: false,
});
});
});
23 changes: 21 additions & 2 deletions extensions/graphql/src/__tests__/unit/graphql.server.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('GraphQL server', () => {

it('registers resolver classes', () => {
server.resolver(RecipeResolver);
expect(server.getResolvers()).to.containEql(RecipeResolver);
expect(server.getResolverClasses()).to.containEql(RecipeResolver);
});

it('registers resolver classes with name', () => {
Expand All @@ -39,10 +39,29 @@ describe('GraphQL server', () => {
return next();
};
server.middleware(middleware);
const middlewareList = await server.getMiddleware();
const middlewareList = await server.getMiddlewareList();
expect(middlewareList).to.containEql(middleware);
});

it('fails to start without resolvers', async () => {
await expect(server.start()).to.be.rejectedWith(
/Empty `resolvers` array property found in `buildSchema` options/,
);
});

it('starts and stops', async () => {
server.resolver(RecipeResolver);
await server.start();
expect(server.listening).to.be.true();
await server.stop();
expect(server.listening).to.be.false();
});

it('does not create http server with asMiddlewareOnly option', async () => {
server = new GraphQLServer({asMiddlewareOnly: true});
expect(server.httpServer).to.be.undefined();
});

function givenServer() {
server = new GraphQLServer();
}
Expand Down
4 changes: 4 additions & 0 deletions extensions/graphql/src/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import {
InputType,
Mutation,
ObjectType,
PubSub,
Query,
Resolver,
Root,
Subscription,
} from 'type-graphql';

/**
Expand All @@ -38,3 +40,5 @@ export const field = Field;
export const inputType = InputType;
export const objectType = ObjectType;
export const authorized = Authorized;
export const subscription = Subscription;
export const pubSub = PubSub;
12 changes: 9 additions & 3 deletions extensions/graphql/src/graphql.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
// License text available at https://opensource.org/licenses/MIT

import {
Application,
Binding,
Component,
config,
CoreBindings,
createBindingFromClass,
inject,
} from '@loopback/core';
import {GraphQLResolverBooter} from './booters/resolver.booter';
import {GraphQLServer} from './graphql.server';
import {GraphQLComponentOptions} from './types';
import {GraphQLBindings} from './keys';

/**
* Component for GraphQL
Expand All @@ -22,5 +24,9 @@ export class GraphQLComponent implements Component {
createBindingFromClass(GraphQLResolverBooter),
];

constructor(@config() private options: GraphQLComponentOptions = {}) {}
constructor(@inject(CoreBindings.APPLICATION_INSTANCE) app: Application) {
app
.configure(GraphQLBindings.GRAPHQL_SERVER)
.toAlias(GraphQLBindings.CONFIG);
}
}
Loading