Добро пожаловать в этот учебник по созданию полнофункционального приложения с помощью Amplication.
Мы будем шаг за шагом создавать приложение Todos
, используя React для фронтенда и Amplication для бэкенда.
Если вы застряли, у вас возникли вопросы или вы просто хотите поздороваться с другими разработчиками Amplication, такими же как вы, то присоединяйтесь к нашему Discord!
Оглавление
- Шаг 1 — GraphQL
- Шаг 2 — Обновление lib/auth.js
- Шаг 3 — Обновление lib/tasks.js
- Шаг 4 — Подведение итогов
Шаг 1 — GraphQL
До сих пор мы взаимодействовали с бэкендом Amplication посредством HTTP-запросов. Однако Amplication предоставляет другой способ взаимодействия с бэкендом — GraphQL. GraphQL — это язык запросов, который позволяет создавать читаемые команды с множеством преимуществ. Если вы хотите узнать больше о том, почему GraphQL может быть лучшим выбором для вашего приложения, я рекомендую прочитать эту статью команды Apollo.
Если вы запустили бэкенд (npm run start:backend
), вы можете поработать с запросами на GraphQL Playground http://localhost:3000/graphql.
Для выполнения GraphQL-запросов к бэкенду мы будем использовать библиотеку @apollo/client
. Сначала установите @apollo/client
как зависимость в подпапку web
:
cd web
npm install @apollo/client
Мы захотим настроить наш GraphQL клиент. Создайте следующий файл web/src/lib/apollo.js
и в верхней части файла импортируйте @apollo/client
.
Затем вставьте следующий код:
import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
const apiUrl = "http://localhost:3000/graphql";
const jwtKey = "accessToken";
const httpLink = createHttpLink({
uri: apiUrl,
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem(jwtKey);
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
});
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
Как и в шаге 4 учебника, @apollo/client
был настроен на получение токена доступа JWT пользователя и присвоение его заголовку Authorization каждого запроса.
Мы также хотим включить функции для проверки существования маркера доступа и для сохранения нового маркера доступа.
export const isStoredJwt = () => Boolean(localStorage.getItem(jwtKey));
export const setStoredJwt = (accessToken) =>
localStorage.setItem(jwtKey, accessToken);
Наконец, мы захотим экспортировать gql
из @apollo/client
. Это позволит писать GraphQL-запросы и мутации.
export { gql } from "@apollo/client";
Шаг 2 — Обновление lib/auth.js
Откройте web/src/lib/auth.js
и удалите весь код в файле. В верхней части файла мы импортируем некоторые функции, которые мы создали в файле web/src/lib/apollo.js
.
import { gql, isStoredJwt, setStoredJwt, client } from "./apollo";
Во-первых, добавьте новую функцию me
:
const GET_ME = gql`
query me {
me {
id
}
}
`;
export const me = async () => {
return isStoredJwt()
? (await client.query({ query: GET_ME }).catch(() => null))?.data.me
: null;
};
Вы заметите, что запрос к учетной записи пользователя разбит на две части: GET_ME
и me
. Первая переменная, GET_ME
— это место, где записывается запрос. Одно из преимуществ GraphQL заключается в том, что мы сообщаем бэкенду, какие данные нам нужны. В данном случае нам нужен только id
пользователя, так что это все, что запрашивает данный запрос.
me
будет фактически выполнять запрос.
Далее добавьте функцию login
:
const LOGIN = gql`
mutation login($credentials: Credentials!) {
login(credentials: $credentials) {
accessToken
}
}
`;
export const login = async (username, password) => {
const result = (
await client
.mutate({
mutation: LOGIN,
variables: { credentials: { username, password } },
})
.catch(() => null)
)?.data.login;
if (!result) {
return alert("Could not login");
}
setStoredJwt(result.accessToken);
return me();
};
Теперь, вместо того чтобы называть это запросом, мы будем называть эту функцию мутацией. Запросы используются для чтения данных, а мутации — для записи данных. Вход в систему и регистрация технически являются записью данных, поскольку в бэкенде создается сессия.
LOGIN
— это мутация, которая принимает имя пользователя
и пароль
пользователя как объект и возвращает только accessToken
из запроса.
login
будет выполнять мутацию подобно реализации HTTP. Вместо того чтобы передавать учетные данные в BODY HTTP-запроса, учетные данные (и другие аргументы в целом) передаются в объекте variables
. Ключевые значения variables
отображаются на имена переменных в mutation
, которые мы пишем. Так, variables.credentials
в client.mutate
отображается на $credentials
в mutation login($credentials: Credentials!)
.
Наконец, добавьте функцию signup
:
const SIGNUP = gql`
mutation signup($credentials: Credentials!) {
signup(credentials: $credentials) {
accessToken
}
}
`;
export const signup = async (username, password) => {
const result = (
await client
.mutate({
mutation: SIGNUP,
variables: { credentials: { username, password } },
})
.catch(() => null)
)?.data.signup;
if (!result) {
return alert("Could not sign up");
}
setStoredJwt(result.accessToken);
return me();
};
Шаг 3 — Обновление lib/tasks.js
Далее нам нужно будет обновить функции задач для использования GraphQL. Откройте web/src/lib/tasks.js
, удалите весь код в файле и замените его следующим:
import { gql, client } from "./apollo";
const CREATE_TASK = gql`
mutation createTask($data: TaskCreateInput!) {
createTask(data: $data) {
completed
createdAt
id
text
}
}
`;
export const create = async (text, uid) => {
const result = (
await client
.mutate({
mutation: CREATE_TASK,
variables: {
data: {
completed: false,
text,
uid: { id: uid },
},
},
})
.catch(() => null)
)?.data.createTask;
if (!result) {
return alert("Could not create task");
}
return result;
};
const GET_TASKS = gql`
query tasks($where: TaskWhereInput, $orderBy: [TaskOrderByInput!]) {
tasks(where: $where, orderBy: $orderBy) {
completed
createdAt
id
text
}
}
`;
export const getAll = async (uid) => {
const result = (
await client
.query({
query: GET_TASKS,
variables: {
where: { uid: { id: uid } },
orderBy: { createdAt: "Asc" },
},
})
.catch(() => null)
)?.data.tasks;
if (!result) {
alert("Could not get tasks");
return [];
}
return result;
};
const UPDATE_TASK = gql`
mutation updateTask($data: TaskUpdateInput!, $where: TaskWhereUniqueInput!) {
updateTask(data: $data, where: $where) {
completed
createdAt
id
text
}
}
`;
export const update = async (task) => {
const result = (
await client
.mutate({
mutation: UPDATE_TASK,
variables: {
data: {
completed: !task.completed,
},
where: {
id: task.id,
},
},
})
.catch(() => null)
)?.data.updateTask;
if (!result) {
return alert("Could not update task");
}
return result;
};
Шаг 4 — Завершение
Запустите приложение и поиграйте!
Задачи пользователей теперь сохраняются в бэкенде Amplication с помощью GraphQL-запросов и мутаций, а не традиционных HTTP-запросов.
Поздравляем разработчика. Возьмите с собой то, чему вы научились, и создайте что-то удивительное.
Если вам нужна помощь или вы хотите поделиться своими наработками, присоединяйтесь к нашему Discord.
Чтобы просмотреть изменения для этого шага, посетите здесь.