GraphQL API с TypeScript: пошаговый туториал — часть 3
In the previous parts of the tutorial, we covered the basics of creating a GraphQL server in TypeScript and learned how to set up simple queries. In this final part, we will delve into advanced techniques: mutations, subscriptions, and error handling. If you are studying TypeScript for beginners and want to master modern programming of APIs, this material will be an excellent step forward. All training is built on practical examples that you can immediately apply in your projects.
1. Implementing mutations with TypeScript
Mutations are data modification operations (create, update, delete). In GraphQL, they are denoted by the keyword mutation. Let's create a mutation for adding a new user.
First, define the type in the GraphQL schema:
type Mutation { addUser(name: String!, email: String!): User!}Now let's implement the resolver in TypeScript. Note the strict typing:
import { Resolver, Mutation, Arg } from 'type-graphql';import { User } from '../entities/User';
@Resolver()export class UserResolver { @Mutation(() => User) async addUser( @Arg('name') name: string, @Arg('email') email: string ): Promise<User> { const user = new User(); user.name = name; user.email = email; await user.save(); return user; }}Important: in TypeScript we use the @Mutation and @Arg decorators from the type-graphql library, which makes the code clean and type-safe. This is especially useful for programming large projects where predictability is important.
2. Subscriptions for real-time updates
Subscriptions allow clients to receive real-time updates via WebSocket. In TypeScript, their implementation requires additional server configuration.
Let's add a subscription for new messages:
import { Subscription, Root } from 'type-graphql';
@Resolver()export class MessageResolver { @Subscription(() => Message, { topics: 'NEW_MESSAGE', }) newMessage(@Root() message: Message): Message { return message; }}To publish an event, use pubSub.publish('NEW_MESSAGE', message). This allows you to create a chat or real-time notifications. Subscriptions are a powerful tool in the arsenal of programming, and TypeScript makes their implementation safe thanks to static typing.
3. Error handling and validation
Proper error handling is key to a reliable API. In TypeScript, we can use custom error classes:
import { ApolloError } from 'apollo-server-express';
export class UserInputError extends ApolloError { constructor(message: string) { super(message, 'USER_INPUT_ERROR'); Object.defineProperty(this, 'name', { value: 'UserInputError' }); }}In resolvers, we validate input data:
@Mutation(() => User)async createUser(@Arg('email') email: string) { if (!email.includes('@')) { throw new UserInputError('Invalid email format'); } // ... creation logic}You can also use validation decorators from class-validator:
import { IsEmail, Length } from 'class-validator';
class CreateUserInput { @Length(2, 50) name: string;
@IsEmail() email: string;}I especially recommend this approach for TypeScript for beginners, as it automates data validation and reduces the number of bugs.
4. Performance optimization
For production projects, it is important to avoid the N+1 problem. Use DataLoader for ```