# GraphQL WS
GraphQL Websocket allows you to use the subscription
feature of GraphQL using the Websocket transport protocol.
This module is based on the graphql-ws (opens new window) package. It pre-configures the socket server and GraphQL server to work together.
# Feature
- Support multiple GraphQL server
- Enable subscription feature of GraphQL
# Installation
This module need to be used with @tsed/apollo
module. So, you must install it before (see here).
import {Configuration} from "@tsed/common";
import "@tsed/platform-express";
import "@tsed/apollo";
import "@tsed/graphql-ws";
import {join} from "path";
@Configuration({
apollo: {
server1: {
// GraphQL server configuration
path: "/",
playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio
plugins: [], // Apollo plugins
wsServerOptions: {
// See options descriptions on
},
wsUseServerOptions: {
// See options descriptions on GraphQL WS
}
// Give custom server instance
// server?: (config: Config) => ApolloServer;
// ApolloServer options
// ...
// See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html
}
},
graphqlWs: {
// global options
wsServerOptions: {
// See options descriptions on
},
wsUseServerOptions: {
// See options descriptions on
}
}
})
export class Server {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Register plugins
You can register plugins with the plugins
property. The plugins are executed in the order of declaration.
import {Configuration} from "@tsed/common";
import "@tsed/platform-express";
import "@tsed/apollo";
import {join} from "path";
@Configuration({
apollo: {
server1: {
plugins: [] // Apollo plugins
}
}
})
export class Server {}
2
3
4
5
6
7
8
9
10
11
12
13
But if you need to register and access to the injector, you can use the $alterApolloServerPlugins
hook. For example,
you can register the graphql-ws
necessary to support the subscription
feature of GraphQL like this:
import {Constant, Inject, InjectorService, Module} from "@tsed/di";
import {useServer} from "graphql-ws/lib/use/ws";
import Http from "http";
import Https from "https";
import {WebSocketServer} from "ws";
import {GraphQLWSOptions} from "./GraphQLWSOptions";
@Module()
export class GraphQLWSModule {
@Constant("graphqlWs", {})
private settings: GraphQLWSOptions;
@Inject(Http.Server)
private httpServer: Http.Server | null;
@Inject(Https.Server)
private httpsServer: Https.Server | null;
@Inject()
private injector: InjectorService;
createWSServer(settings: GraphQLWSOptions) {
const wsServer = new WebSocketServer({
...(this.settings.wsServerOptions || {}),
...settings.wsServerOptions,
server: this.httpsServer || this.httpServer!,
path: settings.path
});
return useServer(
{
...(this.settings.wsUseServerOptions || {}),
...settings.wsUseServerOptions,
schema: settings.schema
},
wsServer
);
}
async $alterApolloServerPlugins(plugins: any[], settings: GraphQLWSOptions) {
const wsServer = await this.createWSServer(settings);
this.injector.logger.info(`Create GraphQL WS server on: ${settings.path}`);
return plugins.concat({
serverWillStart() {
return {
async drainServer() {
await wsServer.dispose();
}
};
}
} as any);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Note
Ts.ED provide a @tsed/graphql-ws
package to support the subscription
feature of GraphQL. See here (opens new window) for more details.
# Nexus
# Installation
Now, we can configure the Ts.ED server by importing @tsed/apollo
in your Server:
import {Configuration} from "@tsed/common";
import "@tsed/platform-express";
import "@tsed/apollo";
import {schema} from "./schema";
import {join} from "path";
@Configuration({
apollo: {
server1: {
// GraphQL server configuration
path: "/",
playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio
schema,
plugins: [] // Apollo plugins
// Give custom server instance
// server?: (config: Config) => ApolloServer;
// ApolloServer options
// ...
// See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html
}
}
})
export class Server {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Then create schema/index.ts
:
import {makeSchema} from "nexus";
import {join} from "path";
export const schema = makeSchema({
types: [], // 1
outputs: {
typegen: join(process.cwd(), "..", "..", "nexus-typegen.ts"), // 2
schema: join(process.cwd(), "..", "..", "schema.graphql") // 3
}
});
2
3
4
5
6
7
8
9
10
# TypeGraphQL
# Installation
To begin, install the @tsed/typegraphql
package:
Now, we can configure the Ts.ED server by importing @tsed/typegraphql
in your Server:
# Types
We want to get the equivalent of this type described in SDL:
type Recipe {
id: ID!
title: String!
description: String
creationDate: Date!
ingredients: [String!]!
}
2
3
4
5
6
7
So we create the Recipe class with all properties and types:
class Recipe {
id: string;
title: string;
description?: string;
creationDate: Date;
ingredients: string[];
}
2
3
4
5
6
7
Then we decorate the class and its properties with decorators:
import {Field, ID, ObjectType} from "type-graphql";
@ObjectType()
export class Recipe {
@Field((type) => ID)
id: string;
@Field()
title: string;
@Field({nullable: true})
description?: string;
@Field()
creationDate: Date;
@Field((type) => [String])
ingredients: string[];
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
The detailed rules for when to use nullable, array and others are described in fields and types docs (opens new window).
# Resolvers
After that we want to create typical crud queries and mutation. To do that we create the resolver (controller) class that will have injected RecipeService in the constructor:
import {Inject} from "@tsed/di";
import {ResolverController} from "@tsed/typegraphql";
import {Arg, Args, Query} from "type-graphql";
import {RecipeNotFoundError} from "../errors/RecipeNotFoundError";
import {RecipesService} from "../services/RecipesService";
import {Recipe} from "../types/Recipe";
import {RecipesArgs} from "../types/RecipesArgs";
@ResolverController(Recipe)
export class RecipeResolver {
@Inject()
private recipesService: RecipesService;
@Query((returns) => Recipe)
async recipe(@Arg("id") id: string) {
const recipe = await this.recipesService.findById(id);
if (recipe === undefined) {
throw new RecipeNotFoundError(id);
}
return recipe;
}
@Query((returns) => [Recipe])
recipes(@Args() {skip, take}: RecipesArgs) {
return this.recipesService.findAll({skip, take});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Multiple GraphQL server
If you register multiple GraphQL servers, you must specify the server id in the @ResolverController
decorator.
@ResolverController(Recipe, {id: "server1"})
Another solution is to not use @ResolverController
(use @Resolver
from TypeGraphQL), and declare explicitly the resolver in the server configuration:
@Configuration({
graphql: {
server1: {
resolvers: {
RecipeResolver
}
},
server2: {
resolvers: {
OtherResolver
}
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
# Data Source
Data source is one of the Apollo server features which can be used as option for your Resolver or Query. Ts.ED provides a DataSourceService decorator to declare a DataSource which will be injected to the Apollo server context.
import {DataSource} from "@tsed/typegraphql";
import {RESTDataSource} from "apollo-datasource-rest";
import {User} from "../models/User";
@DataSource()
export class UserDataSource extends RESTDataSource {
constructor() {
super();
this.baseURL = "https://myapi.com/api/users";
}
getUserById(id: string): Promise<User> {
return this.get(`/${id}`);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Then you can retrieve your data source through the context in your resolver like that:
import {ResolverController} from "@tsed/typegraphql";
import {Arg, Authorized, Ctx, Query} from "type-graphql";
import {UserDataSource} from "../datasources/UserDataSource";
import {User} from "../models/User";
@ResolverController(User)
export class UserResolver {
@Authorized()
@Query(() => User)
public async user(@Arg("userId") userId: string, @Ctx("dataSources") dataSources: any): Promise<User> {
const userDataSource: UserDataSource = dataSources.userDataSource;
return userDataSource.getUserById(userId);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Get Server instance
ApolloService (or TypeGraphQLService) lets you to retrieve an instance of ApolloServer.
import {AfterRoutesInit} from "@tsed/common";
import {Inject, Injectable} from "@tsed/di";
import {ApolloService} from "@tsed/apollo";
import {ApolloServer} from "apollo-server-express";
@Injectable()
export class UsersService implements AfterRoutesInit {
@Inject()
private ApolloService: ApolloService;
// or private typeGraphQLService: TypeGraphQLService;
private server: ApolloServer;
$afterRoutesInit() {
this.server = this.apolloService.get("server1")!;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
For more information about ApolloServer, look at its documentation here (opens new window);
# Testing
Here is an example to create a test server based on TypeGraphQL and run a query:
TIP
The unit example is also available to test any Apollo Server!
# Author
# Maintainers
Last Updated: 10/24/2024, 6:33:40 AM
Other topics
- Session & cookies
- Passport.js
- Keycloak
- Prisma
- TypeORM
- MikroORM
- Mongoose
- GraphQL
- GraphQL WS
- Apollo
- TypeGraphQL
- GraphQL Nexus
- Socket.io
- Swagger
- AJV
- Multer
- Serve static files
- Templating
- Serverless HTTP
- Seq
- OIDC
- Stripe
- Agenda
- Terminus
- Serverless
- Server-sent events
- IORedis
- Vike
- Jest
- Vitest
- Controllers
- Providers
- Model
- JsonMapper
- Middlewares
- Pipes
- Interceptors
- Authentication
- Hooks
- Exceptions
- Throw HTTP Exceptions
- Cache
- Command
- Response Filter
- Injection scopes
- Custom providers
- Lazy-loading provider
- Custom endpoint decorator
- Testing
- Customize 404