Photo by Colombe Photographie Colombe on Unsplash

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.

Frontend Development With JS
6 min readNov 19, 2024

--

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.

--

--

No responses yet