Передісторія
В період, поки пишу цей пост, мої основні задачі на роботі повʼязані з розробкою адмін-панелі та GraphQL Apollo сервера, який спілкується з цією адмінкою. До початку розробки цього функціоналу, у мене не було комерційного досвіду роботу з Apollo GraphQL. Тому довелось детальніше ознайомитися, як працює GrahpQL та як вирішуються типові проблеми-задачі, які виникають під час розробки.
В цьому пості опишу одну з проблем, яка виникла при роботі з датами
В чому ж проблема?
Один з сервісів, до якого звертається Apollo GraphQL сервер, віддає дані для поля з датою unix-timestamp в секундах, наприклад 1677445679. При цьому, фронтенд очікує, що це поле буде в мілісекундах, для коректного відображення.
Можемо зауважити дві цікавинки:
- Конструктор
new Date(value)очікує, щоvalueбуде в мілісекундах - Величина
unix-timestampзалежить від мови програмування. Одні повертають секунди, інші — мілісекунди. Приклад, як дістатиtimestampв залежності від мови — посилання
Варіанти вирішення
Для себе відмітив три місця, де можна конвертувати секунди в мілісекунди:
- На клієнтській частині додатка
- Конвертувати на
apollo serverв resolvers - Ввести кастомний скалярний тип на
apollo server
Конвертація на клієнтській частині додатка
Від цього способу відмовився, адже намагаюсь мінімально оперувати конвертацією даних на клієнті. Також, конвертацію доводилось би робити в кожному місці, де використовується поле з датою, а таких місць більш як 10. Якщо одного дня, бекенд почне повертати дату в мілісекундах, то код конвертації доведеться прибирати з цих місць
Конвертація в resolvers
Від цього способу також відмовився. Є декілька endpoints, які повертають дату в секундах, яку потрібно конвертувати. З кожним новим endpoints, де зустрічається дата, потрібно памʼятати про конвертацію
Використання кастомного скалярного типу
Зупинився на цьому рішення. Про нього поговоримо далі, але перед цим хочу трохи розповісти, що ж таке кастомні скалярні типи в apollo graphql
Custom Scalars
Специфікація GraphQL визначає декілька скалярних типів, за допомогою яких можна описати значення полів схеми — Int, Float, String, Boolean та ID. Якщо ж цих типів недостатньо, то можна описати свій. Наприклад, можна описати тип, який буде описувати MAC-адресу
Як створити Custom Scalar?
-
Потрібно додати в будь-яке місце
graphqlсхеми свійcustom scalar. Наприклад:scalar SpecificTime -
Визначити, як
Apollo Serverбуде взаємодіяти зcustom scalar. Це відбувається завдяки класуGraphQLScalarType.const SpecificTime = new GraphQLScalarType({
name: 'SpecificTime',
description: 'This type convert time from seconds to milliseconds',
serialize(value) {},
parseValue(value) {},
parseLiteral(ast) {},
});Клас
GraphQLScalarTypeочікує обʼєкт з полямиnameтаdescription. А також три методи для обробки значення:
-
serialize— метод викликається, колиApollo Serverнадсилає дані до клієнта. Тому в цьому місці можемо валідувати і конвертуватиvalueу зручний формат -
parseValue— метод обробляє значення, яке надіслано з клієнта доApollo Server, перед тим, як воно буде доступне вresolvers. Важливо, що цей метод викликається, коли кастомний скалярний тип передається як аргумент доqueryquery ($first: Int) {
allUsers(first: $first) {
id
}
} -
parseLiteral— метод викликається в тому ж випадку, що іparseValue. Єдина відмінність — що значення кастомного типу має бути захаркоджено вquery:query {
allUsers(first: 10) {
id
}
}
-
Передати створений скаляр з пункту 2 в
Apollo Serverчерезresolvers:const typeDefs = gql`
scalar SpecificTime
`;
const resolvers = {
SpecificTime: specificTime,
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
Фінальне рішення
Отож, як створити свій кастомний тип вже зрозуміло. Як виглядає рішення для конвертації значень певних полів з секунди у мілісекунди:
import { gql } from 'graphql-tag';
import { GraphQLScalarType, GraphQLError } from 'graphql';
import secondsToMilliseconds from 'date-fns/secondsToMilliseconds';
const SpecificTime = new GraphQLScalarType({
name: 'SpecificTime',
description: 'This type convert time from seconds to milliseconds',
serialize(value: unknown) {
if (typeof value !== 'number') {
throw new GraphQLError(
'GraphQL Date Scalar serializer for SpecificTime expected a `number` value'
);
}
return secondsToMilliseconds(value);
},
});
В цьому випадку в класі GraphQLScalarType описав лише метод serialize, оскільки для поточних задач потрібно лише відображати дату на клієнтів. Передавати ж дату з клієнта не потрібно
Приклад використання кастомного типу:
type Device {
id: ID!
serialNumber: String!
lastActiveDate: SpecificTime!
}
Корисні посилання
- Документація по custom scalars від apollo server
- Набір custom scalars від the guild. Наприклад, є описані типи для
IPv4,JWT,PhoneNumber,RGBта інших