REACT ARCHITECTURE SERIES
React Proje Mimarisi 27 — (Public APIs ve Mock API)
Bu blog yazısında React örneklerinde kullanabileceğim public API’ler ile kendi oluşturduğumuz Mock API’leri inceliyeceğiz.
Bu blog yazısında React örneklerinde veya testlerinde kullanabileceğimiz public API’leri bir araya toplamaya çalışacağım.
Bunun ile birlikte örneklerde kendi kullanabileceğimiz kendi Mock API’lerimizi nasıl oluşturduğumu anlatacağım.
Common Waiting Methods
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
Fetch işlemleri sırasında genel bir yavaşlatma sağlar.
// Artificially delay the response so we can better see loading states.
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const globalFetch = window.fetch
window.fetch = async (...args) => {
await delay(1000)
return globalFetch(...args)
}
Web API Navigator
navigator.mediaDevices.enumerateDevices()
Fetch API
Fetch API verilen bir URL veri çekmek ve güncellemek için kullanabileceğiniz bir Web API’dir.
const promise = fetch(url, options)
Pokemon API
Bu yalnızca tüketime yönelik bir API’dir — kaynaklarda yalnızca HTTP GET yöntemi kullanılabilir.
Bu API’ye erişmek için kimlik doğrulaması gerekmez ve tüm kaynaklar tamamen açık ve kullanılabilir durumdadır. Kasım 2018'de statik barındırmaya geçildiğinden beri, hız sınırlaması tamamen kaldırılmıştır
https://pokeapi.co/docs/v2#info
https://pokeapi.co/api/v2/pokemon/${id}
Hacker News API
https://hn.algolia.com/api
https://hn.algolia.com/api/v1/search?query=${query}&tags=${encodeURIComponent(tag)}&page=${page}
https://news.ycombinator.com/${itemId}
Simple Success API
() => Promise.resolve("Spider Man")
() => Promise.resolve(3)
() => Promise.resolve(Math.random())
Waiting with Timeout
async function getData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
title: "AAA",
authors: ["BBB"],
thumbnail: "https://onurdayibasi.dev/images/CCC.jpg",
});
}, 1000);
});
}
GitHub API
https://api.github.com/orgs/${username}/repos
https://api.github.com/orgs/${username}/public_members
https://api.github.com/repos/${username}/${repo}/issues
https://api.github.com/orgs/${username}/repos?sort=created
https://api.github.com/orgs/${username}/repos?sort=updated
https://api.github.com/orgs/${username}/repos?sort=pushed
https://api.github.com/orgs/${username}/repos?sort=full_name
https://api.github.com/search/issues?${searchParams}
https://api.github.com/search/issues?q=${search} is:issue repo:${username}/query
`https://api.github.com/orgs/${username}/repos
?sort=${sort}
&per_page=${pagesize}
&page=${page}`
https://api.github.com/repos/${username}/query/milestones/${id}
Countries API
https://restcountries.com/v3.1/name/{name}
UUID API
https://uuid.rocks/json
Dev.to API
https://dev.to/api/articles
https://dev.to/api/articles${path}
https://dev.to/api/articles?per_page=${pagesize}&page=${page}
Todos API
Local
Success
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
let todos = [
{ id: '1', title: 'Learn JavaScript', done: true },
{ id: '2', title: 'Go shopping', done: false },
{ id: '3', title: 'Learn React ', done: true },
{ id: '4', title: 'Learn React Query', done: false },
]
export async function fetchTodos(sort) {
await sleep(750)
return Promise.resolve(
structuredClone(todos).sort((a, b) => {
if (String(a[sort]).toLowerCase() < String(b[sort]).toLowerCase()) {
return -1
}
if (String(a[sort]).toLowerCase() > String(b[sort]).toLowerCase()) {
return 1
}
return 0
})
)
}
export async function toggleTodo(id) {
await sleep(750)
todos = todos.map((t) => t.id === id ? { ...t, done: !t.done } : t)
return Promise.resolve(todos.find(t => t.id === id))
}
export async function addTodo(newTitle) {
await sleep(750)
const newTodo = {
id: String(todos.length + 1),
title: newTitle,
done: false,
}
todos.push(newTodo)
return Promise.resolve(newTodo)
}
Local Error
export async function fetchTodos() {
await sleep(1000)
throw new Error('no todos were found.')
}
MSW Handlers
MSW(Mock Server Worker) nasıl kullandığımı burada yazmıştım. Aynı zamanda kütüphane yazarının oluşturduğu bir eğitim var. Mock REST and GraphQL APIs with Mock Service Worker bundan da faydalanabilirsiniz.
export const restApiUri = 'https://api.sampledomain.dev';
Cities — Districts — Neighborhoods
export const neighborhoods = [
{label: 'Beşiktaş_Mahalle1', value: 'IST_BSK_MH1'},
{label: 'Beşiktaş_Mahalle2', value: 'IST_BSK_MH2'},
{label: 'Kadıköy_Mahalle1', value: 'IST_KAD_MH1'},
{label: 'Kadıköy_Mahalle2', value: 'IST_KAD_MH2'},
{label: 'Beyoğlu_Mahalle1', value: 'IST_BEY_MH1'},
{label: 'Beyoğlu_Mahalle2', value: 'IST_BEY_MH2'},
{label: 'Çankaya_Mahalle1', value: 'ANK_CANK_MH1'},
{label: 'Çankaya_Mahalle2', value: 'ANK_CANK_MH2'},
{label: 'Keçiören_Mahalle1', value: 'ANK_KEC_MH1'},
{label: 'Keçiören_Mahalle2', value: 'ANK_KEC_MH2'},
{label: 'Yenimahalle_Mahalle1', value: 'ANK_YNM_MH1'},
{label: 'Yenimahalle_Mahalle2', value: 'ANK_YNM_MH2'},
];
export const districts = [
{label: 'Beşiktaş', value: 'IST_BSK'},
{label: 'Kadıköy', value: 'IST_KAD'},
{label: 'Beyoğlu', value: 'IST_BEY'},
{label: 'Çankaya', value: 'ANK_CANK'},
{label: 'Keçiören', value: 'ANK_KEC'},
{label: 'Yenimahalle', value: 'ANK_YNM'},
];
export const cities = [
{label: 'Istanbul', value: 'IST'},
{label: 'Ankara', value: 'ANK'},
];
export const cityHandlers = [
http.get(`${restApiUri}/address/cities`, async () => {
return HttpResponse.json(cities);
}),
http.get(`${restApiUri}/address/districts`, async () => {
return HttpResponse.json(districts);
}),
http.get(`${restApiUri}/address/neighborhoods`, async () => {
return HttpResponse.json(neighborhoods);
}),
];
SignIn Handler
export const signHandlers = [
http.post(`${restApiUri}/sign/rememberme`, async () => {
return HttpResponse.json({
success: true,
message: 'Password Send Your Email and Phone',
});
}),
];
Task Handler with DB
export const db = factory({
tasks: {
id: primaryKey(faker.string.uuid),
title: String,
isDone: Boolean,
},
});
export const taskHandlers = [
http.get(`${restApiUri}/tasks`, async () => {
return HttpResponse.json(db.tasks.getAll());
}),
http.get(`${restApiUri}/tasks/:id`, async ({request}) => {
const {id} = request.params;
return HttpResponse.json(db.tasks.getById(id));
}),
http.post(`${restApiUri}/tasks`, async ({request}) => {
const taskObj = await request.json();
const newEntity = db.tasks.create(taskObj);
return HttpResponse.json(newEntity);
}),
http.patch(`${restApiUri}/tasks`, async ({request}) => {
const {idsToDelete} = await request.json();
const deletedTasks = db.tasks.deleteMany({
where: {
id: {
in: idsToDelete,
},
},
});
return HttpResponse.json(deletedTasks);
}),
http.put(`${restApiUri}/tasks/:id`, async ({params, request}) => {
const {id} = params;
const taskObj = await request.json();
const updatedTask = db.tasks.update({
where: {
id: {
equals: id,
},
},
data: taskObj,
});
return HttpResponse.json(updatedTask);
}),
http.delete(`${restApiUri}/tasks/:id`, async ({params}) => {
const {id} = params;
db.tasks.delete({
where: {
id: {
equals: id,
},
},
});
return new HttpResponse('Succeed', {status: 200, statusText: 'OK'});
}),
];
User Handler
// ======= USER DATAPROVIDER =======
export const db = factory({
users: {
id: primaryKey(faker.string.uuid),
username: String,
age: Number,
active: Boolean,
email: String,
avatar: String,
password: String,
birthdate: Date,
registeredAt: Date,
roles: ['roles'], // Create a relationship between users and roles
},
roles: {
id: primaryKey(faker.string.uuid),
name: String,
description: String,
},
});
export const userHandlers = [
http.get(`${restApiUri}/users`, async () => {
return HttpResponse.json(db.users.getAll());
}),
http.get(`${restApiUri}/users/:id`, ({params}) => {
const {id} = params;
return HttpResponse.json(db.users.getById(id));
}),
http.post(`${restApiUri}/users`, async ({request}) => {
const userObj = await request.json();
const newEntity = db.users.create(userObj);
return HttpResponse.json(newEntity);
}),
http.patch(`${restApiUri}/users`, async ({request}) => {
const {idsToDelete} = await request.json();
const deletedTasks = db.users.deleteMany({
where: {
id: {
in: idsToDelete,
},
},
});
return HttpResponse.jsonjson(deletedTasks);
}),
http.put(`${restApiUri}/users/:id`, async ({request, params}) => {
const {id} = params;
const userObj = await request.json();
const updatedTask = db.users.update({
where: {
id: {
equals: id,
},
},
data: userObj,
});
return HttpResponse.json(updatedTask);
}),
http.delete(`${restApiUri}/users/:id`, async ({params}) => {
const {id} = params;
db.users.delete({
where: {
id: {
equals: id,
},
},
});
return new HttpResponse('Succeed', {status: 200, statusText: 'OK'});
}),
];
// ======= ROLE DATAPROVIDER =======
export const roleHandlers = [
http.get(`${restApiUri}/users/:id/roles`, ({params}) => {
const {id} = params;
const user = db.users.findFirst({
where: {
id: {
equals: id,
},
},
});
const roles = user.roles;
return HttpResponse.json(roles);
}),
];
Kanban Board
Aşağıdaki kod bloğu bu adresten alınmıştır
import { delay, http, HttpResponse } from "msw";
import { z } from "zod";
export const itemSchema = z.object({
id: z.string(),
title: z.string(),
content: z.string().optional(),
order: z.coerce.number(),
columnId: z.string().uuid(),
boardId: z.coerce.number(),
});
export const columnSchema = z.object({
id: z.string().uuid(),
boardId: z.coerce.number(),
name: z.string(),
order: z.number(),
});
export const boardSchema = z.object({
id: z.coerce.number(),
name: z.string(),
color: z.string(),
columns: z.array(columnSchema),
items: z.array(itemSchema),
});
let extraDelay = 0;
export function setExtraDelay(delay: number) {
extraDelay = delay;
}
export const updateSchema = z.union([
z
.object({ intent: z.literal("updateBoardName") })
.merge(boardSchema.pick({ id: true, name: true })),
z
.object({ intent: z.literal("updateColumn") })
.merge(columnSchema.pick({ id: true, name: true })),
]);
export const deleteItemSchema = itemSchema.pick({ id: true, boardId: true });
export const newColumnSchema = columnSchema.omit({ order: true });
export type Board = z.infer<typeof boardSchema>;
export type Column = z.infer<typeof columnSchema>;
export type Item = z.infer<typeof itemSchema>;
const board: Board = {
id: 1,
name: "First board",
color: "#e0e0e0",
columns: [],
items: [],
};
const upsertItem = (item: Item) => {
const existingItem = board.items.find((i) => i.id === item.id);
if (existingItem) {
Object.assign(existingItem, item);
} else {
board.items.push(item);
}
};
export const handlers = [
http.post("/board/newColumn", async ({ request }) => {
const newColumn = newColumnSchema.parse(await request.json());
board.columns = [
...board.columns,
{ ...newColumn, order: board.columns.length + 1 },
];
await delay();
await delay(extraDelay);
return HttpResponse.json({ ok: true });
}),
http.post("/board/newItem", async ({ request }) => {
const newItem = itemSchema.parse(await request.json());
upsertItem(newItem);
await delay();
await delay(extraDelay);
return HttpResponse.json({ ok: true });
}),
http.post("/board/deleteItem", async ({ request }) => {
const { id } = deleteItemSchema.parse(await request.json());
board.items = board.items.filter((item) => item.id !== id);
await delay();
await delay(extraDelay);
return HttpResponse.json({ ok: true });
}),
http.post("/board/moveItem", async ({ request }) => {
const item = itemSchema.parse(await request.json());
upsertItem(item);
await delay();
await delay(extraDelay);
return HttpResponse.json({ ok: true });
}),
http.post("/board/update", async ({ request }) => {
const payload = updateSchema.parse(await request.json());
if (payload.intent === "updateBoardName") {
board.name = payload.name;
} else if (payload.intent === "updateColumn") {
const column = board.columns.find((c) => c.id === payload.id);
if (column) {
column.name = payload.name;
}
}
await delay();
await delay(extraDelay);
return HttpResponse.json({ ok: true });
}),
http.get("/board/*", async () => {
await delay();
await delay(extraDelay);
return HttpResponse.json(board);
}),
];
Okumaya Devam Et 😃
Bu yazının devamı veya yazı grubundaki (react-mimarisi) diğer yazılara erişmek için bu linke tıklayabilirsiniz.
Bu yazının devamı veya yazı grubundaki (networking) diğer yazılara erişmek için bu linke tıklayabilirsiniz.