In business projects today, the front-end and the back-end are built separately, so each part can be more specialized and more professional. These two talk to each other through an API. In this article I want to show how we can build a real project in this way. For this I kept everything very simple and I designed basic APIs, so we can follow the main road without getting lost in complex things.
Setting up the project
Setting up the backend
For the backend of this project we use the same project that we set up in the part “Building the front-end of a news site the old way with Django”.
So, if you set that project up before, you only need to run it. If not, you can follow the setup steps from here.
Setting up the front-end
For the front-end of this project I prepared a template. To download it, click on the button below.

Getting to know the project
After you set the project up and run it, open the address below to see the list of all the APIs that you need.
http://localhost:8000/swagger/
Everything that we build in this part is done in the front-end codebase.
Task one – building the home page
Task one description
Build the home page so the final result looks like the picture below.

Doing task one – building the home page
Task one is simpler than the other tasks, but it is bigger. To make it easier for us, it is better to split this task into some parts, like below.
- Part one: first, showing the news list, which is the most important part of the page.
- Part two: then, building the pagination buttons, because without them we cannot see the other news.
- Part three: and at the end, the search and filter form
I made this order from how simple and how important each part of this page is.
Task one, part one – building the news list
Open the index.html file and put the code below in it.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz — News List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="add-news.html" class="btn btn-primary">➕ Add News</a>
</header>
<main class="container">
<div class="page-header">
<h2>📋 Latest News</h2>
</div>
<h2>Add your content here</h2>
</main>
<script>
fetch("http://localhost:8000/api/news")
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((err) => console.error(err));
</script>
</body>
</html>In the code above:
- Lines 22 to 29: we made a script tag, so we can put js code in it. The browser reads the code of this file line by line and runs it, and at the end it runs the javascript that we wrote in this tag.
- Line 23: with the function
fetchwe ask the backend for the news list. - Line 24: we wait for the answer of the backend.
- Line 25: we wait until the answer of the backend becomes json.
- Line 26: we print the answer of the backend, which is now json, in the console of the browser.
- Line 28: if one of the steps above has a problem, we print the error message in the console.
Now if you open this page in the browser and you go to the console tab, you see the output below.

In the picture above:
- The first part shows the answer that came from the backend.
- The second part shows the address of the code where this output came from. Here it says that this output is from line 26 of the index.html file.
Now build the code above like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz — News List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="add-news.html" class="btn btn-primary">➕ Add News</a>
</header>
<main class="container">
<div class="page-header">
<h2>📋 Latest News</h2>
</div>
<div id="result"></div>
</main>
<script>
fetch("http://localhost:8000/api/news")
.then((response) => response.json())
.then((data) => {
const container = document.getElementById("result");
data.results.forEach((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
var newsCard = `
<div class="news-row">
<img class="news-thumb" src="${imgSrc}">
<div class="news-info">
<a href="news-detail.html?id=${news.id}" class="title-link">${news.title}</a>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
<a href="news-detail.html?id=${news.id}" class="detail-btn">Details →</a>
</div>
`;
container.insertAdjacentHTML("beforeend", newsCard);
});
})
.catch((err) => console.error(err));
</script>
</body>
</html>In the code above:
- Line 19: we made a div for showing the output of the api.
- Line 26: we choose the div that we made for showing the output.
- Line 27: we made a loop on the news that we took from the backend.
- Line 28: if a news item has no picture, we give it a default picture.
- Lines 29 to 41: we take the code of the news card as a template, we fill it with the news information and we save it in a variable with the name newsCard.
- Line 42: we add newsCard to the div that we made for showing the result.
The output of the code above is like below.

Congratulations! The first part of task one is finished.
Task one, part two – the pagination buttons
If we put page=2? in the api address, the second page of the news list is shown.
<script>
fetch("http://localhost:8000/api/news?page=2")
.then((response) => response.json())
.then((data) => {
const container = document.getElementById("result");
data.results.forEach((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
var newsCard = `
<div class="news-row">
<img class="news-thumb" src="${imgSrc}">
<div class="news-info">
<a href="news-detail.html?id=${news.id}" class="title-link">${news.title}</a>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
<a href="news-detail.html?id=${news.id}" class="detail-btn">Details →</a>
</div>
`;
container.insertAdjacentHTML("beforeend", newsCard);
});
})
.catch((err) => console.error(err));
</script>To be able to show the other pages, we only need to change this number in a dynamic way with a button. For this, change the code like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz - News List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="add-news.html" class="btn btn-primary">➕ Add News</a>
</header>
<main class="container">
<div class="page-header">
<h2>📋 Latest News</h2>
</div>
<div id="result"></div>
<div class="pagination">
<a onclick="handlePage(1)" href="#!">1</a>
<a onclick="handlePage(2)" href="#!">2</a>
<a onclick="handlePage(3)" href="#!">3</a>
</div>
</main>
<script>
function handlePage (pageNumber=1) {
fetch("http://localhost:8000/api/news?page=" + pageNumber)
.then((response) => response.json())
.then((data) => {
const container = document.getElementById("result");
data.results.forEach((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
var newsCard = `
<div class="news-row">
<img class="news-thumb" src="${imgSrc}">
<div class="news-info">
<a href="news-detail.html?id=${news.id}" class="title-link">${news.title}</a>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
<a href="news-detail.html?id=${news.id}" class="detail-btn">Details →</a>
</div>
`;
container.insertAdjacentHTML("beforeend", newsCard);
});
})
.catch((err) => console.error(err));
}
handlePage()
</script>
</body>
</html>In the code above we made a function with the name handlePage that takes the page number and shows the news of that page.
- Lines 20 to 24: we made some buttons for moving between the pages. Each of these buttons calls
handlePagewith the right input. - Line 28: the function
handlePageis made here, and the number 1 is its default input value. - Line 29: we give the input of the function, which is called
pageNumber, to the api address, so the backend sends us the page that we want. - Line 53: we call the function that we made one time ourselves, without any click on a button, so the page is not empty when it loads for the first time.
Now the problem is that the news list becomes longer with every click on the buttons. This is good when we want to build a “show more” button, but here we want pagination, so this is not useful for us now.
To stop the news list from becoming longer, and to show the news of the new page instead of the news of the old page, change the code like below.
<script>
function handlePage (pageNumber=1) {
fetch("http://localhost:8000/api/news?page=" + pageNumber)
.then((response) => response.json())
.then((data) => {
const container = document.getElementById("result");
var cards = ''
data.results.forEach((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
cards += `
<div class="news-row">
<img class="news-thumb" src="${imgSrc}">
<div class="news-info">
<a href="news-detail.html?id=${news.id}" class="title-link">${news.title}</a>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
<a href="news-detail.html?id=${news.id}" class="detail-btn">Details →</a>
</div>
`;
});
container.innerHTML = cards
})
.catch((err) => console.error(err));
}
handlePage()
</script>In the code above we save all the cards that must be shown on one page in a variable with the name cards , and at the end, outside the loop on line 50, we put them in the container as the HTML content.
Now the problem is that we can move only between the first 3 pages. There is no way to go to page 4 and after it. To solve this problem, change the code like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz - News List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="add-news.html" class="btn btn-primary">➕ Add News</a>
</header>
<main class="container">
<div class="page-header">
<h2>📋 Latest News</h2>
</div>
<div id="result"></div>
<div id="pagination" class="pagination"></div>
</main>
<script>
const PAGE_SIZE = 4;
var paginationContainer = document.getElementById("pagination");
function handlePage(pageNumber = 1) {
fetch("http://localhost:8000/api/news?page=" + pageNumber)
.then((response) => response.json())
.then((data) => {
var pageCount = Math.floor(data.count / PAGE_SIZE) + 1;
var paginationButtons = ''
if (pageCount === 2) {
paginationButtons = `
<a onclick="handlePage(1)" class="${pageNumber === 1 && 'active'}" href="#!">1</a>
<a onclick="handlePage(2)" class="${pageNumber === 2 && 'active'}" href="#!">2</a>`;
} else if (pageCount > 2 && pageNumber === 1) {
paginationButtons = `
<a onclick="handlePage(1)" class="active" href="#!">1</a>
<a onclick="handlePage(2)" href="#!">2</a>
<a onclick="handlePage(3)" href="#!">3</a>`;
} else {
paginationButtons = `
<a onclick="handlePage(${pageNumber - 1})" href="#!">${pageNumber - 1}</a>
<a onclick="handlePage(${pageNumber})" class="active" href="#!">${pageNumber}</a>
<a onclick="handlePage(${pageNumber + 1})" href="#!">${pageNumber + 1}</a>`;
}
paginationContainer.innerHTML = paginationButtons;
const container = document.getElementById("result");
var cards = "";
data.results.forEach((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
cards += `
<div class="news-row">
<img class="news-thumb" src="${imgSrc}">
<div class="news-info">
<a href="news-detail.html?id=${news.id}" class="title-link">${news.title}</a>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
<a href="news-detail.html?id=${news.id}" class="detail-btn">Details →</a>
</div>
`;
});
container.innerHTML = cards;
})
.catch((err) => console.error(err));
}
handlePage();
</script>
</body>
</html>In the code above
- Line 20: we gave an id to the div and we cleaned its content, so later we can take it with javascript and add the buttons inside it.
- Line 24: we save the number of items on each page for the next calculations.
- Line 31: we calculate the number of all the pages.
- Line 32: we made a variable to put the code of the pagination buttons in it.
- Lines 33 to 36: if we have only two pages, we make only two buttons, one for going to the first page and one for going to the second page.
- Lines 37 to 41: if we have more than two pages and we are showing the first page, we make 3 buttons for going to the first, second and third page.
- Lines 42 to 47: in the other case (if we have more than two pages and we are showing a page that is not the first one), we make 3 buttons like below.
- The first button for going one page back (
pageNumber - 1) - The second button for showing the page that we are on (
pageNumber) - The third button for showing the next page (
pageNumber + 1)
- The first button for going one page back (
Thanks God, at the end we could build the pagination. The only problem now is that this pagination is not exactly like the thing that the task asked for. The task wants Next and Previous buttons. Now it is time to build these buttons too.
To add the Next and Previous buttons, change the code like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz - News List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="add-news.html" class="btn btn-primary">➕ Add News</a>
</header>
<main class="container">
<div class="page-header">
<h2>📋 Latest News</h2>
</div>
<div id="result"></div>
<div id="pagination" class="pagination"></div>
</main>
<script>
const PAGE_SIZE = 4;
var paginationContainer = document.getElementById("pagination");
function handlePage(pageNumber = 1) {
fetch("http://localhost:8000/api/news?page=" + pageNumber)
.then((response) => response.json())
.then((data) => {
var pageCount = Math.floor(data.count / PAGE_SIZE) + 1;
var paginationButtons = ''
if (pageCount === 2) {
paginationButtons = `
<a onclick="handlePage(1)" class="${pageNumber === 1 && 'active'}" href="#!">1</a>
<a onclick="handlePage(2)" class="${pageNumber === 2 && 'active'}" href="#!">2</a>`;
} else if (pageCount > 2 && pageNumber === 1) {
paginationButtons = `
<span class="disabled">← Previous</span>
<a onclick="handlePage(1)" class="active" href="#!">1</a>
<a onclick="handlePage(2)" href="#!">2</a>
<a onclick="handlePage(3)" href="#!">3</a>
<a onclick="handlePage(2)" href="#!">Next →</a>`;
} else {
paginationButtons = `
<a onclick="handlePage(${pageNumber - 1})" href="#!">← Previous</a>
<a onclick="handlePage(${pageNumber - 1})" href="#!">${pageNumber - 1}</a>
<a onclick="handlePage(${pageNumber})" class="active" href="#!">${pageNumber}</a>
<a onclick="handlePage(${pageNumber + 1})" href="#!">${pageNumber + 1}</a>
<a onclick="handlePage(${pageNumber + 1})" href="#!">Next →</a>`;
}
paginationContainer.innerHTML = paginationButtons;
const container = document.getElementById("result");
var cards = "";
data.results.forEach((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
cards += `
<div class="news-row">
<img class="news-thumb" src="${imgSrc}">
<div class="news-info">
<a href="news-detail.html?id=${news.id}" class="title-link">${news.title}</a>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
<a href="news-detail.html?id=${news.id}" class="detail-btn">Details →</a>
</div>
`;
});
container.innerHTML = cards;
})
.catch((err) => console.error(err));
}
handlePage();
</script>
</body>
</html>Congratulations! This part is finished at last.
Task one, part three – the search form
If you change the code of the page like below, all the news that has the word National in its title or its description is shown.
<script>
const PAGE_SIZE = 4;
var paginationContainer = document.getElementById("pagination");
function handlePage(pageNumber = 1) {
fetch("http://localhost:8000/api/news?page=" + pageNumber + '&search=National')
.then((response) => response.json())
.then((data) => {
var pageCount = Math.floor(data.count / PAGE_SIZE) + 1;
var paginationButtons = ''
if (pageCount === 2) {
paginationButtons = `
<a onclick="handlePage(1)" class="${pageNumber === 1 && 'active'}" href="#!">1</a>
<a onclick="handlePage(2)" class="${pageNumber === 2 && 'active'}" href="#!">2</a>`;
} else if (pageCount > 2 && pageNumber === 1) {
paginationButtons = `
<span class="disabled">← Previous</span>
<a onclick="handlePage(1)" class="active" href="#!">1</a>
<a onclick="handlePage(2)" href="#!">2</a>
<a onclick="handlePage(3)" href="#!">3</a>
<a onclick="handlePage(2)" href="#!">Next →</a>`;
} else {
paginationButtons = `
<a onclick="handlePage(${pageNumber - 1})" href="#!">← Previous</a>
<a onclick="handlePage(${pageNumber - 1})" href="#!">${pageNumber - 1}</a>
<a onclick="handlePage(${pageNumber})" class="active" href="#!">${pageNumber}</a>
<a onclick="handlePage(${pageNumber + 1})" href="#!">${pageNumber + 1}</a>
<a onclick="handlePage(${pageNumber + 1})" href="#!">Next →</a>`;
}
paginationContainer.innerHTML = paginationButtons;
const container = document.getElementById("result");
var cards = "";
data.results.forEach((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
cards += `
<div class="news-row">
<img class="news-thumb" src="${imgSrc}">
<div class="news-info">
<a href="news-detail.html?id=${news.id}" class="title-link">${news.title}</a>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
<a href="news-detail.html?id=${news.id}" class="detail-btn">Details →</a>
</div>
`;
});
container.innerHTML = cards;
})
.catch((err) => console.error(err));
}
handlePage();
</script>In the code above, by putting &search=National in the address of the request, only the news that has the word National in its title or its description is shown.
If we can do this in a dynamic way with javascript, the search is finished.
To build the search, change the code like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz - News List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="add-news.html" class="btn btn-primary">➕ Add News</a>
</header>
<main class="container">
<div class="page-header">
<h2>📋 Latest News</h2>
</div>
<div class="toolbar">
<input type="text" name="search" class="search-input" placeholder="Search by title..." />
<button onclick="handleSearch()" class="apply-btn">🔍 Apply Filters</button>
</div>
<div id="result"></div>
<div id="pagination" class="pagination"></div>
</main>
<script>
var SEARCH_TERM = "";
function handleSearch() {
var searchInput = document.querySelector('[name="search"]');
handlePage(1, searchInput.value);
}
const PAGE_SIZE = 4;
var paginationContainer = document.getElementById("pagination");
function handlePage(pageNumber = 1, searchTerm = "") {
fetch("http://localhost:8000/api/news?page=" + pageNumber + "&search=" + searchTerm)
.then((response) => response.json())
.then((data) => {
var pageCount = Math.floor(data.count / PAGE_SIZE) + 1;
var paginationButtons = "";
if (pageCount === 2) {
paginationButtons = `
<a onclick="handlePage(1)" class="${pageNumber === 1 && "active"}" href="#!">1</a>
<a onclick="handlePage(2)" class="${pageNumber === 2 && "active"}" href="#!">2</a>`;
} else if (pageCount > 2 && pageNumber === 1) {
paginationButtons = `
<span class="disabled">← Previous</span>
<a onclick="handlePage(1)" class="active" href="#!">1</a>
<a onclick="handlePage(2)" href="#!">2</a>
<a onclick="handlePage(3)" href="#!">3</a>
<a onclick="handlePage(2)" href="#!">Next →</a>`;
} else {
paginationButtons = `
<a onclick="handlePage(${pageNumber - 1})" href="#!">← Previous</a>
<a onclick="handlePage(${pageNumber - 1})" href="#!">${pageNumber - 1}</a>
<a onclick="handlePage(${pageNumber})" class="active" href="#!">${pageNumber}</a>
<a onclick="handlePage(${pageNumber + 1})" href="#!">${pageNumber + 1}</a>
<a onclick="handlePage(${pageNumber + 1})" href="#!">Next →</a>`;
}
paginationContainer.innerHTML = paginationButtons;
const container = document.getElementById("result");
var cards = "";
data.results.forEach((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
cards += `
<div class="news-row">
<img class="news-thumb" src="${imgSrc}">
<div class="news-info">
<a href="news-detail.html?id=${news.id}" class="title-link">${news.title}</a>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
<a href="news-detail.html?id=${news.id}" class="detail-btn">Details →</a>
</div>
`;
});
container.innerHTML = cards;
})
.catch((err) => console.error(err));
}
handlePage();
</script>
</body>
</html>
In the code above
- Lines 19 to 22: we made a form (without a form tag) with an input for writing the searched text and a button for submitting it.
- Line 28: we made a variable to keep the value that is searched.
- Line 29: we built a function with the name
handleSearchthat is called every time somebody clicks on the button. - Line 30: we take the input whose name attribute is search.
- Line 31: we call the function
handlePagein a way that it shows the first page that holds our search. - Line 37: we changed the function
handlePageso we can send the search value to it as an input. - Line 38: we add the searched value to our request to the api too.
Now the search works, but if you search something and then you click on the pagination buttons to see the next pages, you see that the search is not used on the other pages. The reason is that these buttons only send the page number to the function handlePage and they do not send the searched word.
To solve this problem, change the code like below.
function handlePage(pageNumber = 1, searchTerm = "") {
fetch("http://localhost:8000/api/news?page=" + pageNumber + "&search=" + searchTerm)
.then((response) => response.json())
.then((data) => {
var pageCount = Math.floor(data.count / PAGE_SIZE) + 1;
var paginationButtons = "";
if (pageCount === 2) {
paginationButtons = `
<a onclick="handlePage(1, '${searchTerm}')" class="${pageNumber === 1 && "active"}" href="#!">1</a>
<a onclick="handlePage(2, '${searchTerm}')" class="${pageNumber === 2 && "active"}" href="#!">2</a>`;
} else if (pageCount > 2 && pageNumber === 1) {
paginationButtons = `
<span class="disabled">← Previous</span>
<a onclick="handlePage(1, '${searchTerm}')" class="active" href="#!">1</a>
<a onclick="handlePage(2, '${searchTerm}')" href="#!">2</a>
<a onclick="handlePage(3, '${searchTerm}')" href="#!">3</a>
<a onclick="handlePage(2, '${searchTerm}')" href="#!">Next →</a>`;
} else {
paginationButtons = `
<a onclick="handlePage(${pageNumber - 1}, '${searchTerm}')" href="#!">← Previous</a>
<a onclick="handlePage(${pageNumber - 1}, '${searchTerm}')" href="#!">${pageNumber - 1}</a>
<a onclick="handlePage(${pageNumber}, '${searchTerm}')" class="active" href="#!">${pageNumber}</a>
<a onclick="handlePage(${pageNumber + 1}, '${searchTerm}')" href="#!">${pageNumber + 1}</a>
<a onclick="handlePage(${pageNumber + 1}, '${searchTerm}')" href="#!">Next →</a>`;
}There is still one more problem. If we search something that has less than 5 results, the pagination buttons have a problem, because in this situation all the results are on one page, and we did not think about a way to show one page correctly.
To solve this problem, change the code like below.
function handlePage(pageNumber = 1, searchTerm = "") {
fetch("http://localhost:8000/api/news?page=" + pageNumber + "&search=" + searchTerm)
.then((response) => response.json())
.then((data) => {
var pageCount = Math.floor(data.count / PAGE_SIZE) + 1;
var paginationButtons = "";
if (pageCount === 1) {
paginationButtons = ''
} else if (pageCount === 2) {
paginationButtons = `
<a onclick="handlePage(1, '${searchTerm}')" class="${pageNumber === 1 && "active"}" href="#!">1</a>
<a onclick="handlePage(2, '${searchTerm}')" class="${pageNumber === 2 && "active"}" href="#!">2</a>`;
} else if (pageCount > 2 && pageNumber === 1) {
paginationButtons = `
<span class="disabled">← Previous</span>
<a onclick="handlePage(1, '${searchTerm}')" class="active" href="#!">1</a>
<a onclick="handlePage(2, '${searchTerm}')" href="#!">2</a>
<a onclick="handlePage(3, '${searchTerm}')" href="#!">3</a>
<a onclick="handlePage(2, '${searchTerm}')" href="#!">Next →</a>`;
} else {
paginationButtons = `
<a onclick="handlePage(${pageNumber - 1}, '${searchTerm}')" href="#!">← Previous</a>
<a onclick="handlePage(${pageNumber - 1}, '${searchTerm}')" href="#!">${pageNumber - 1}</a>
<a onclick="handlePage(${pageNumber}, '${searchTerm}')" class="active" href="#!">${pageNumber}</a>
<a onclick="handlePage(${pageNumber + 1}, '${searchTerm}')" href="#!">${pageNumber + 1}</a>
<a onclick="handlePage(${pageNumber + 1}, '${searchTerm}')" href="#!">Next →</a>`;
}
paginationContainer.innerHTML = paginationButtons;In the code above we made it so the pagination buttons are not shown when the number of results is less than one page.
Now it is time for the filtering. To add the possibility of filtering the news by its category, change the code like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz - News List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="add-news.html" class="btn btn-primary">➕ Add News</a>
</header>
<main class="container">
<div class="page-header">
<h2>📋 Latest News</h2>
</div>
<div class="toolbar">
<input type="text" name="search" class="search-input" placeholder="Search by title..." />
<select name="category" class="filter-select">
<option value="">All Categories</option>
<option value="cultural">Cultural</option>
<option value="political">Political</option>
<option value="sports">Sports</option>
<option value="scientific">Scientific</option>
</select>
<button onclick="handleSearch()" class="apply-btn">🔍 Apply Filters</button>
</div>
<div id="result"></div>
<div id="pagination" class="pagination"></div>
</main>
<script>
var SEARCH_TERM = "";
function handleSearch() {
var searchInput = document.querySelector('[name="search"]');
var category = document.querySelector('[name="category"]');
handlePage(1, searchInput.value, category.value);
}
const PAGE_SIZE = 4;
var paginationContainer = document.getElementById("pagination");
function handlePage(pageNumber = 1, searchTerm = "", category = "") {
fetch("http://localhost:8000/api/news?page=" + pageNumber + "&search=" + searchTerm + '&category=' + category)
.then((response) => response.json())
.then((data) => {
var pageCount = Math.floor(data.count / PAGE_SIZE) + 1;
var paginationButtons = "";
if (pageCount === 1) {
paginationButtons = "";
} else if (pageCount === 2) {
paginationButtons = `
<a onclick="handlePage(1, '${searchTerm}')" class="${pageNumber === 1 && "active"}" href="#!">1</a>
<a onclick="handlePage(2, '${searchTerm}')" class="${pageNumber === 2 && "active"}" href="#!">2</a>`;
} else if (pageCount > 2 && pageNumber === 1) {
paginationButtons = `
<span class="disabled">← Previous</span>
<a onclick="handlePage(1, '${searchTerm}')" class="active" href="#!">1</a>
<a onclick="handlePage(2, '${searchTerm}')" href="#!">2</a>
<a onclick="handlePage(3, '${searchTerm}')" href="#!">3</a>
<a onclick="handlePage(2, '${searchTerm}')" href="#!">Next →</a>`;
} else {
paginationButtons = `
<a onclick="handlePage(${pageNumber - 1}, '${searchTerm}')" href="#!">← Previous</a>
<a onclick="handlePage(${pageNumber - 1}, '${searchTerm}')" href="#!">${pageNumber - 1}</a>
<a onclick="handlePage(${pageNumber}, '${searchTerm}')" class="active" href="#!">${pageNumber}</a>
<a onclick="handlePage(${pageNumber + 1}, '${searchTerm}')" href="#!">${pageNumber + 1}</a>
<a onclick="handlePage(${pageNumber + 1}, '${searchTerm}')" href="#!">Next →</a>`;
}
paginationContainer.innerHTML = paginationButtons;
const container = document.getElementById("result");
var cards = "";
data.results.forEach((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
cards += `
<div class="news-row">
<img class="news-thumb" src="${imgSrc}">
<div class="news-info">
<a href="news-detail.html?id=${news.id}" class="title-link">${news.title}</a>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
<a href="news-detail.html?id=${news.id}" class="detail-btn">Details →</a>
</div>
`;
});
container.innerHTML = cards;
})
.catch((err) => console.error(err));
}
handlePage();
</script>
</body>
</html>In the code above:
- Lines 21 to 27: with select-option we made a menu for choosing the categories.
- Line 38: we take the chosen value by its name attribute.
- Line 39: we send the chosen value as the third input to the function
handlePage. - Line 45: we added the possibility of taking category as the third input of the function
handlePage. - Line 46: we send the chosen category to the backend.
Congratulations! Now the categories are added too and this page is finished.
Task two – building the detail page
Task two description
Build the news detail page so the final result looks like the picture below.

Doing task two
First we make the look of the page with fake information. For this, write the code below in the news-detail.html page.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz — News Detail</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="index.html" class="btn btn-outline">← Back to List</a>
</header>
<main class="container">
<div class="detail-card">
<a href="index.html" class="back-link">← Back to all news</a>
<div class="detail-header">
<img src="https://placehold.co/400x250/0f5858/white?text=Parliament" alt="News image" />
<div class="detail-title">
<h1>Parliament Budget Session Highlights</h1>
<div class="meta">
<span>📅 2026-05-15</span>
<span class="category-badge cat-political">Political</span>
</div>
</div>
</div>
<div class="description">
Lawmakers convened today to debate the annual budget proposal, focusing on healthcare and infrastructure
spending. The session extended late into the evening with heated discussions over tax reforms. The final vote
is expected next week.
</div>
</div>
</main>
</body>
</html>With the code above the look of the page is fine, but the problem is that it does not matter which news item you open. This same content is shown for all the news. To load the look of this page with the content of the news item, change the code like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz — News Detail</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="index.html" class="btn btn-outline">← Back to List</a>
</header>
<main class="container">
<div class="detail-card">
<a href="index.html" class="back-link">← Back to all news</a>
<div id="news"></div>
</div>
</main>
<script>
const params = new URLSearchParams(window.location.search);
const newsId = params.get('id');
fetch("http://localhost:8000/api/news/" + newsId)
.then((response) => response.json())
.then((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
var newsHTML = `
<div class="detail-header">
<img src="${imgSrc}" />
<div class="detail-title">
<h1>${news.title}</h1>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
</div>
<div class="description">
${news.description}
</div>`
var newsContainer = document.getElementById('news')
newsContainer.innerHTML = newsHTML
})
</script>
</body>
</html>In the code above:
- Line 18: we made a div, so later we can add the code of the news to it with javascript.
- Lines 22 and 23: imagine that the id of a news item is 300. If the user came from the news list page to the detail page by clicking on the detail button,
?id=300is in the page address and says which news item we want to show. This line takes the id of the news. - Lines 24 to 45: with the id that we took before, we take that news item from the backend, then we build its html code and at the end we put it in the div that we took on line 18.
Now, to add the edit and the delete button, change the code like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz — News Detail</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="index.html" class="btn btn-outline">← Back to List</a>
</header>
<main class="container">
<div class="detail-card">
<a href="index.html" class="back-link">← Back to all news</a>
<div id="news"></div>
</div>
</main>
<script>
const params = new URLSearchParams(window.location.search);
const newsId = params.get("id");
fetch("http://localhost:8000/api/news/" + newsId)
.then((response) => response.json())
.then((news) => {
const imgSrc = news.image || `https://placehold.co/150x150/0f5858/white?text=${news.category}`;
var newsHTML = `
<div class="detail-header">
<img src="${imgSrc}" />
<div class="detail-title">
<h1>${news.title}</h1>
<div class="meta">
<span>📅 ${news.publish_date}</span>
<span class="category-badge cat-${news.category.toLowerCase()}">${news.category}</span>
</div>
</div>
</div>
<div class="description">
${news.description}
</div>
<br />
<div class="btn btn-danger" onclick="handleDelete(${newsId})">
🗑 Delete This Article
</div>
<a href="add-news.html?id=${newsId}" class="btn detail-btn">
Edit
</a>`;
var newsContainer = document.getElementById("news");
newsContainer.innerHTML = newsHTML;
});
function handleDelete(newsId) {
fetch("http://localhost:8000/api/news/" + newsId + "/", {
method: "DELETE",
})
.then((response) => {
if (response.status === 204) {
window.location.href = "index.html";
} else {
alert("There is a problem with deleting item!");
}
})
.then((data) => console.log(data));
}
</script>
</body>
</html>In the code above:
- Line 43: with the tag
<br />we made a little space above the buttons. - Lines 44 to 46: a button that runs the function
handleDeletewhen somebody clicks on it, and deletes the news. - Lines 47 to 49: a button that opens the edit page of the news and sends the id of the news to that page.
- Lines 55 to 67: a function that calls the delete address of the news with the method
DELETE, and if the news is deleted, it sends the user to the news list page.- Line 57: here we say the method. Without this line, the backend does not understand that we want to delete the news.
- Line 60: if the news is deleted, the backend gives the number 204 as the status. So here, with a condition, we can understand if the news is really deleted or not. If it is deleted, we send the user to the news list page with the code on line 61. If not, we show an error message and we tell the user that the work was not successful.
Task three – the edit news page
Task three description
Build the edit news page so the final result looks like the picture below.

Doing task three
Open the add-news.html page and change the code like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz — Add News</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="index.html" class="btn btn-outline">← Back to List</a>
</header>
<main class="container">
<div class="form-card">
<h2>📝 Add New Article</h2>
<form action="#" method="post" enctype="multipart/form-data">
<div class="form-grid">
<div class="form-group full-width">
<label for="title">Title *</label>
<input type="text" id="title" name="title" placeholder="Enter news title" required="" />
</div>
<div class="form-group full-width">
<label for="desc">Description *</label>
<textarea id="desc" name="description" placeholder="Full article text..." required=""></textarea>
</div>
<div class="form-group full-width">
<label for="img">Image</label>
<input type="file" id="img" name="image" accept="image/*" />
</div>
<div class="form-group">
<label for="date">Publish Date *</label>
<input type="date" id="date" name="publish_date" required="" />
</div>
<div class="form-group">
<label for="category">Category *</label>
<select id="category" name="category" required="">
<option value="">-- Select --</option>
<option value="cultural">Cultural</option>
<option value="political">Political</option>
<option value="sports">Sports</option>
<option value="scientific">Scientific</option>
</select>
</div>
<div class="form-group full-width">
<label class="checkbox-wrap">
<input type="checkbox" id="draft" name="saveAsDraft" />
Save as Draft
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 Save Article</button>
<a href="index.html" class="btn btn-outline">Cancel</a>
</div>
</form>
</div>
</main>
</body>
</html>The code above makes the look of the page like the task asked, but this page still does nothing. To give this page a soul, we use javascript and we change the code like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz — Add News</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="index.html" class="btn btn-outline">← Back to List</a>
</header>
<main class="container">
<div class="form-card">
<h2>📝 Add New Article</h2>
<form action="#" method="post" enctype="multipart/form-data" onsubmit="handleSubmit(event)">
<div class="form-grid">
<div class="form-group full-width">
<label for="title">Title *</label>
<input type="text" id="title" name="title" placeholder="Enter news title" required="" />
</div>
<div class="form-group full-width">
<label for="desc">Description *</label>
<textarea id="desc" name="description" placeholder="Full article text..." required=""></textarea>
</div>
<div class="form-group full-width">
<label for="img">Image</label>
<input type="file" id="img" name="image" accept="image/*" />
</div>
<div class="form-group">
<label for="date">Publish Date *</label>
<input type="date" id="date" name="publish_date" required="" />
</div>
<div class="form-group">
<label for="category">Category *</label>
<select id="category" name="category" required="">
<option value="">-- Select --</option>
<option value="cultural">Cultural</option>
<option value="political">Political</option>
<option value="sports">Sports</option>
<option value="scientific">Scientific</option>
</select>
</div>
<div class="form-group full-width">
<label class="checkbox-wrap">
<input type="checkbox" id="draft" name="saveAsDraft" />
Save as Draft
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 Save Article</button>
<a href="index.html" class="btn btn-outline">Cancel</a>
</div>
</form>
</div>
</main>
<script>
const params = new URLSearchParams(window.location.search);
const newsId = params.get("id");
function handleSubmit(e) {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
fetch(`http://localhost:8000/api/news/${newsId}/`, {
method: "PATCH",
body: formData,
})
.then((response) => response.json())
.then((result) => {
window.location.href = "index.html";
})
.catch((error) => {
console.error(error);
alert("❌ Error while saving the news: " + error.message);
});
}
</script>
</body>
</html>In the code above:
- Line 18: on this line we tell the form to run the function
handleSubmitevery time it is submitted. - Lines 61 and 62: in this part we take the id of the news from the page address, so later we can use it on line 70.
- Lines 64 to 82: in this part the function
handleSubmitis built.- Line 64:
eis the event that is sent to this function as an input when the form is submitted. Because this event runs when somebody clicks on the submit button,epoints to this button. Because pressing this button sends the form information in the default way and refreshes the page, we usee.preventDefault()to stop the default behaviour, so we can submit this form ourselves with the api and stop the page from refreshing. - Lines 67 and 68: we take the information that the user wrote in the form and we put it in
formData. - Lines 70 to 73: we send
formDataas the body (the form information) with the methodPATCHto the backend. - Line 76: after the form is submitted, we send the user to the news list page.
- Lines 79 and 80: if there is an error while saving the news, we show it.
- Line 64:
The news is shown from the newest to the oldest. So, to see a news item on the first page after you edit it, set the publish date to today when you edit it.
Now we can edit a news item, but there is a problem. When the form opens, there is no default value in it, and the form must be filled with the values of the news. For this, add the code below to the end of the script tag.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz — Add News</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="index.html" class="btn btn-outline">← Back to List</a>
</header>
<main class="container">
<div class="form-card">
<h2>📝 Add New Article</h2>
<form action="#" method="post" enctype="multipart/form-data" onsubmit="handleSubmit(event)">
<div class="form-grid">
<div class="form-group full-width">
<label for="title">Title *</label>
<input type="text" id="title" name="title" placeholder="Enter news title" required="" />
</div>
<div class="form-group full-width">
<label for="desc">Description *</label>
<textarea id="desc" name="description" placeholder="Full article text..." required=""></textarea>
</div>
<div id="image-container" class="form-group full-width">
<label for="img">Image</label>
<input type="file" id="img" name="image" accept="image/*" />
</div>
<div class="form-group">
<label for="date">Publish Date *</label>
<input type="date" id="date" name="publish_date" required="" />
</div>
<div class="form-group">
<label for="category">Category *</label>
<select id="category" name="category" required="">
<option value="">-- Select --</option>
<option value="cultural">Cultural</option>
<option value="political">Political</option>
<option value="sports">Sports</option>
<option value="scientific">Scientific</option>
</select>
</div>
<div class="form-group full-width">
<label class="checkbox-wrap">
<input type="checkbox" id="draft" name="saveAsDraft" />
Save as Draft
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 Save Article</button>
<a href="index.html" class="btn btn-outline">Cancel</a>
</div>
</form>
</div>
</main>
<script>
const params = new URLSearchParams(window.location.search);
const newsId = params.get("id");
function handleSubmit(e) {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
fetch(`http://localhost:8000/api/news/${newsId}/`, {
method: "PATCH",
body: formData,
})
.then((response) => response.json())
.then((result) => {
window.location.href = "index.html";
})
.catch((error) => {
console.error(error);
alert("❌ Error while saving the news: " + error.message);
});
}
fetch("http://localhost:8000/api/news/" + newsId)
.then((response) => response.json())
.then((data) => {
document.querySelector('h2').innerText = '📝 ' + data.title;
document.querySelector('[name="title"]').value = data.title;
document.querySelector('[name="description"]').value = data.description;
document.querySelector('[name="publish_date"]').value = data.publish_date;
document.querySelector('[name="category"]').value = data.category;
document.querySelector('[name="saveAsDraft"]').checked = data.is_draft;
document
.getElementById("image-container")
.insertAdjacentHTML("beforeend", `<a href="${data.image}">View Image</a>`);
});
</script>
</body>
</html>
In the code above:
- Line 28: on this line we give an id to this tag, so later we can take it with javascript and show the default value of the picture field in it.
- Lines 84 to 96: we take the details of this news from the backend and we put them in the form.
- Line 87: here we put the title of the news at the top of the page too, not only in the input.
- Lines 88 to 92: the values of the fields are asked from the backend and put in the front-end.
- Lines 93 to 95: because the File Upload Field cannot have a default value for security reasons, we use javascript to make a link that shows the default value of the file.
Task four – the add news page
Task four description
Build the add news page so its result looks like the picture below.

Doing task four
Change the code of the add-news.html page like below.
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>codebaz - Add News</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="navbar">
<a href="index.html" class="brand">📰 codebaz</a>
<a href="index.html" class="btn btn-outline">← Back to List</a>
</header>
<main class="container">
<div class="form-card">
<h2>📝 Add New Article</h2>
<form action="#" method="post" enctype="multipart/form-data" onsubmit="handleSubmit(event)">
<div class="form-grid">
<div class="form-group full-width">
<label for="title">Title *</label>
<input type="text" id="title" name="title" placeholder="Enter news title" required="" />
</div>
<div class="form-group full-width">
<label for="desc">Description *</label>
<textarea id="desc" name="description" placeholder="Full article text..." required=""></textarea>
</div>
<div id="image-container" class="form-group full-width">
<label for="img">Image</label>
<input type="file" id="img" name="image" accept="image/*" />
</div>
<div class="form-group">
<label for="date">Publish Date *</label>
<input type="date" id="date" name="publish_date" required="" />
</div>
<div class="form-group">
<label for="category">Category *</label>
<select id="category" name="category" required="">
<option value="">-- Select --</option>
<option value="cultural">Cultural</option>
<option value="political">Political</option>
<option value="sports">Sports</option>
<option value="scientific">Scientific</option>
</select>
</div>
<div class="form-group full-width">
<label class="checkbox-wrap">
<input type="checkbox" id="draft" name="saveAsDraft" />
Save as Draft
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">💾 Save Article</button>
<a href="index.html" class="btn btn-outline">Cancel</a>
</div>
</form>
</div>
</main>
<script>
const params = new URLSearchParams(window.location.search);
const newsId = params.get("id");
function handleSubmit(e) {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
var url;
if (newsId) {
url = `http://localhost:8000/api/news/${newsId}/`
} else {
url = `http://localhost:8000/api/news/`
}
var method = newsId ? 'PATCH' : 'POST'
fetch(url, {
method: method,
body: formData,
})
.then((response) => response.json())
.then((result) => {
window.location.href = "index.html";
})
.catch((error) => {
console.error(error);
alert("❌ Error while saving the news: " + error.message);
});
}
if (newsId) {
fetch("http://localhost:8000/api/news/" + newsId)
.then((response) => response.json())
.then((data) => {
document.querySelector('h2').innerText = '📝 ' + data.title;
document.querySelector('[name="title"]').value = data.title;
document.querySelector('[name="description"]').value = data.description;
document.querySelector('[name="publish_date"]').value = data.publish_date;
document.querySelector('[name="category"]').value = data.category;
document.querySelector('[name="saveAsDraft"]').checked = data.is_draft;
document
.getElementById("image-container")
.insertAdjacentHTML("beforeend", `<a href="${data.image}">View Image</a>`);
});
}
</script>
</body>
</html>In the code above:
- Lines 70 to 75:
urlis set fromnewsId, so we can handle the two different addresses, one for making a new news item and one for editing a news item. - Line 77: if
newsIdhas a value, the method is PATCH; if not, it is POST. - Line 93: if
newsIdhas a value, the form is filled; if not, we do not need to fill the form.
Congratulations! This task is finished too.
Summary
The main goal of this part was to show you, as a backend developer, how the front-end is built. On this road we used JavaScript and a REST API to build a simple news website, and we looked in practice at ideas like taking data from the server, handling the state on the client side and making a dynamic user interface.
It is good to say that this build is more for understanding the workflow, the problems and the common patterns on the front-end side than for real production projects. For serious projects it is better to use the standard frameworks and tools of this area.