# Mastering API Requests: Using Fetch with Strapi's Content API Please copy-paste this in a new HackMD file and share it with dessire.ugarte-amaya@strapi.io. - **meta-title**: Mastering API Requests: Using Fetch with Strapi's Content API - **meta-description**: Learn more about using fetch() with Strapi's Content API. - **Figma images**: ![Mastering API Requests: Using Fetch with Strapi's Content API](https://hackmd.io/_uploads/S10KrgQI0.jpg) ![Mastering API Requests: Using Fetch with Strapi's Content API](https://hackmd.io/_uploads/BJt7Sl780.png) - **Publish date**: - **Reviewers**: Strapi is a free and open-source Headless content management system that focuses on managing and delivering content through APIs. One of the main advantages of Strapi CMS is its ability to automatically create a REST API endpoint when a new content type is created. The API allows you to easily interact with the CMS and retrieve or manipulate your data via API calls. In this article, you'll explore how to use thee `fetch()` method of th Fetch API to interact with the content API. The method will help you make HTTP requests in JavaScript. You will use it to retrieve or manipulate data via Strapi's Content API. By the end of this article, you will have all the skills you need to use its `fetch()` method for retrieving and manipulating data from Strapi in different use cases. ## Prerequisites To comfortably follow along with this guide, you need to have: * [NodeJs](https://nodejs.org/en/download/package-manager) installed in your system * Basic Knowledge of [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) ## Understanding Fetch The `fetch()` method is part of the Fetch API. It is supported in all modern browsers and `Node.js` environments. With the `fetch()` method, you can make requests and fetch resources. ### What is the Fetch API? The **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** is a Promise-based API for making HTTP requests in JavaScript. It is widely used as it provides an easier way of performing network operations compared to the older [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) method. This article will utilize the `fetch()` method from this API which has a lot of pros compared to other methods. ### Advantages of Using `fetch()` Method Over Other Methods The `fetch()` method outshines older methods like `XMLHttpRequest` in several ways: * **Simpler syntax**: It has a cleaner syntax, making it easier to read and write code. * **Promise-based**: It returns a Promise. A Promise is an object that represents the eventual completion or failure of an asynchronous operation. This helps in implementing robust error-handling functions. * **Better cross-origin support**: With support for cross-origin requests out of the box, it eliminates the need for additional configurations. * **Better streaming support**: With the `fetch()` method, you can receive chunks of a response as soon as they are available. You do not have to wait for the entire response to complete. This greatly improves the performance of applications that deal with large corporate of data. To appreciate how simple it is to get started with `fetch()` method, take a look at its basic syntax. ### Basic Syntax and Usage of the `fetch()` Method The syntax involves four main steps: * **Step 1:** You pass the URL of the resource you want to fetch. * **Step 2:** Then you parse the response returned by the `fetch()` method. * **Step 3:** You handle any errors that might have occurred during the process. * **Step 4:** Finally, if the fetching was successful, you do something with your data. Take a look at the basic example below. ```javascript fetch('https://api.example.com/data') .then(response => response.json()) // Parse the response as JSON .then(data => { // Do something with the data console.log(data); }) .catch(error => { // Handle any errors console.error('Error:', error); }); ``` The code shows a high-level overview of how to use the `fetch()` method. Let us now have a look at how you can utilize Fetch with Strapi's content API. ## Setting up Strapi To access the content API in Strapi, you need to create a collection type. After this, you can add fields and data to the collection type. ### Strapi Installation Start by installing Strapi via `npm` at any folder of your choice: ```bash npx create-strapi-app@latest my-project ``` When the installation is done, navigate to the folder where you installed Strapi and run the following command to launch Strapi. ```bash npm run develop ``` The command will start the Strapi server. To access the admin panel, navigate to the Strapi admin panel default URL `http://localhost:1337/admin` on your browser. ![admin-registration](https://hackmd.io/_uploads/r11Qd5n4C.jpg) Signup in order to access the admin dashboard: ![admin-dashboard](https://hackmd.io/_uploads/rJF9dqhEA.png) ### Create Collection Type Next, you need to configure a content type. Let's configure a collection type. Start by naming the collection. You will give it the name `strapi-content`. ![image](https://hackmd.io/_uploads/S1hC4snE0.png) Then create the field types that will hold your data. For this tutorial, you will build a simple collection type that can serve an online course system. Your field types should match the following. Refer to [configuring fields content typeRefer](https://docs.strapi.io/user-docs/content-type-builder/configuring-fields-content-type) to learn more. ![image](https://hackmd.io/_uploads/rkC9WS7SA.png) In the image above, we created two Collection type fields, the `CourseTitle` and `CourseDescription` and type `Text` respectively. ### Enable API Public Access You have to configure Strapi to allow you to send API requests. After creating the fields, proceed to ***Settings > Users & Permissions Plugin > Roles > Public*** then choose `Strapi-content` under **permissions**, this represents the Collection type that we created above. ![image](https://hackmd.io/_uploads/H1DzKNQrA.png) Select all options listed as checkboxes and click the "Save" button. Selecting the options will allow you to send unauthenticated API call requests to Strapi. We will go through how to make API calls for each of the options. Later on, you will learn how to make authenticated calls. ![image](https://hackmd.io/_uploads/rJik5E7HR.png) The `strapi-content` Collection type which you created is now exposed. Proceed to the http://localhost:1337/api/strapi-contents endpoint and see the result below. ![image](https://hackmd.io/_uploads/HyPLQF38C.png) The API JSON should be similar to the one above since you have not added any data to your collection. Let us go through how you can use `fetch()` method to make API calls that will add data to your collection, and then perform retrieval, updation, and deletion actions on the data. ## Setting Up Your Environment To begin working with Strapi's Content API, you'll need to set up a dedicated project environment. Follow these steps: 1. Create a new folder for your project and name it `mastering_strapi`. This will serve as the root directory for your project. 1. Launch your preferred Integrated Development Environment (IDE) and open the `mastering_strapi` folder. 1. Within the root of your project folder, create a new file named `strapi_content_api.js`. This file will contain all the API call code examples we'll be working with. Each API call covered in this guide is independent. This means you can test and run each call separately as needed. 1. Run the code using `node` by executing the following command on your terminal: ``` node strapi_content_api.js ``` This command will execute the JavaScript file and perform any API calls you will implement. ## Sending POST Requests with Fetch To add data to your collection, you have to create entries to your collection. You can achieve this by making a `POST` requests to your API endpoint using the `fetch()` method. ```javascript // ./strapi_content_api.js // Function to create a new entry async function createEntry(data) { const url = "http://localhost:1337/api/strapi-contents"; try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ data }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || "Network response was not ok"); } const jsonResponse = await response.json(); console.log("Success:", jsonResponse); } catch (error) { console.error("Error:", error); } } // Data for the entries const entries = [ { CourseTitle: "Introduction to Strapi", CourseDescription: "A beginner-friendly course introducing the basics of Strapi, a powerful headless CMS.", }, { CourseTitle: "Strapi for Content Management", CourseDescription: "A comprehensive course on using Strapi for effective content management.", }, { CourseTitle: "Building APIs with Strapi", CourseDescription: "Learn how to build robust APIs quickly using Strapi and improve your web development skills.", }, { CourseTitle: "Securing Strapi Applications", CourseDescription: "A course focused on the security aspects of Strapi applications, including authentication and permissions.", }, { CourseTitle: "Deploying Strapi on Cloud", CourseDescription: "A practical course on deploying Strapi applications on various cloud platforms for high availability and scalability.", }, ]; // Create each entry entries.forEach((entry) => createEntry(entry)); ``` In the above code, you define an asynchronous function that sends a POST request to your Strapi **API endpoint** `/api/strapi-contents` with the data for a new entry. The function specifies that the data is to be sent in a **JSON** format. The function then handles the response received from the Strapi backend. If the response status code is in the range `200-299` the function fetches the response data using `response.json()` and logs it to the console. If there is an error during the when the request is made, a new `Error` object is thrown with the **error message** or the **predefined message**. Make sure that the data you are sending to Strapi contain the predefined collection fields and that there are no typos. Now, run the server with the command to make the `POST` request. ```bash node strapi_content_api.js ``` When we run the code above, we should see a successful response in the terminal. To confirm this, if you proceed to the Strapi endpoint `http://localhost:1337/api/strapi-contents` in your browser, the API JSON should include the uploaded data. ![image](https://hackmd.io/_uploads/BJqFrFn80.png) Now, let's see how you can retrieve this data. ## Making GET Requests with the `fetch()` method ### Retrieve Collection Entries with The `fetch()` Method in Strapi For the data to be consumed by your application you need to retrieve it. To achieve this, go ahead and use the `fetch()` method to make the `GET` request to the Strapi API endpoint. We will look at how to retrieve the full data and then specific collection fields. ```javascript // ./strapi_content_api.js // ./strapi_content_api.js // Function to retrieve entries async function getEntries() { const url = "http://localhost:1337/api/strapi-contents"; try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", }, }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || "Network response was not ok"); } const jsonResponse = await response.json(); const entries = jsonResponse.data; // Accessing the 'data' array console.log("Data retrieved:", entries); // retrieve specific collection fields /* const courseTitles = entries.map(entry => entry.CourseTitle); console.log('Course Titles:', courseTitles); // console.log('Data retrieved:', entries);*/ } catch (error) { console.error("Error:", error); } } getEntries(); ``` The code above defines a function that makes a `GET` request to the Strapi API endpoint `/api/strapi-contents` to retrieve data. If there is an error during the fetching process, the function throws a new Error with the **error message** or the **predefined message**. Let us see the actual logic of how to retrieve the data. In Strapi's response structure, the actual data is contained in the `data` property of the `jsonResponse` object. So, when the function receives the API data from the API endpoint it extracts the `data` array from the response. The extracted `data` array looks like this: ![image](https://hackmd.io/_uploads/Hkkhxv7HR.png) If you want to access the data of a specific field in your collection, uncomment the **retrieve specific collection fields** code. It maps over the entries array to extract specific properties, such as **CourseTitle**. Replace the **CourseTitle** with your desired field. ### Retrieve a Specific Entry with The `fetch()` Method in Strapi If you want to retrieve a specific entry, modify the URL to include the entry's ID. Strapi content API lets you access a specific entry by appending the entry's ID to your endpoint URL. Here is how you can modify the function above. ```javascript async function getEntry(entryId) { const url = `http://localhost:1337/api/strapi-contents/${entryId}`; // rest of code remains the same } ``` Then while calling the function pass the ID of the entry you want to retrieve, in this case `entryId`. Now that you know how to add and retrieve data in Strapi using the `fetch()` method, let's see how you can update or delete specific entries. ## Update and Delete Entries with `fetch()` in Strapi ### Update an Entry with The `fetch()` method in Strapi To update an entry in Strapi, you need the ID of the entry. Let's see how you can update the entry with an `id` number of 4 by making a `PUT` request using the `fetch()` method. ```javascript // ./strapi_content_api.js // Function to update an entry async function updateEntry(entryId) { const url = `http://localhost:1337/api/strapi-contents/${entryId}`; const updatedData = { data: { CourseTitle: "Advanced Programming", CourseDescription: "An advanced course on programming concepts.", }, }; try { const response = await fetch(url, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(updatedData), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || "Network response was not ok"); } const jsonResponse = await response.json(); console.log("Update successful:", jsonResponse); } catch (error) { console.error("Error:", error); } } updateEntry(4); // Replace with your preferred entry ID ``` The code above defines a function that takes `entryId` as an argument. The `entryId` represents the ID of an entry. The function is responsible for making a `PUT` request to your **Strapi content API endpoint** at `/api/strapi-contents` to update an existing entry. The function constructs a new URL that contains the id of the entry you want to update. It then uses the `fetch()` method to make a `PUT` request to the endpoint with the provided URL and request options. The data or details of an entry is replaced with the data you pass during the `PUT` request. This completes the update process. If there is an error during the update, the error data is fetched from the response, and a new `Error` object is thrown with the error message or a default message. If the response is `OK`, the function fetches the response data using`response.json()` and logs it to the console. Take a look at how the first entry is updated with the data you passed. ![image](https://hackmd.io/_uploads/rJAtoDXH0.png) You can see the update was successful. Let's see how we can now delete a specific entry. ### Delete an Entry With `fetch()` in Strapi To delete an entry you should pass the entry's ID as we did during entry update. But instead of passing the `PUT` method you should pass the `DELETE` method of the `fetch()` method. ```javascript // ./strapi_content_api.js // Function to delete an entry async function deleteEntry(entryId) { const url = `http://localhost:1337/api/strapi-contents/${entryId}`; try { const response = await fetch(url, { method: "DELETE", headers: { "Content-Type": "application/json", }, }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || "Network response was not ok"); } console.log("Delete successful"); } catch (error) { console.error("Error:", error); } } deleteEntry(5); // Replace with your preffered entry ID ``` The function will delete the entry with the ID number `5`. For the error handling, it uses the same exact logic as explained in the code for updating an entry. Take a look at the new `API` data. ![image](https://hackmd.io/_uploads/ry1eADQHR.png) You can see the entry with `id` number 5 was successfully deleted. You have now covered how to make non-authenticated requests to the Strapi contents API endpoint. But what about when you want all the above API calls to be performed by authorized persons only? Strapi content API endpoint has you covered. ## Making Authenticated API with `fetch()` in Strapi To make sure that API calls are authenticated you need to the configure Strapi content API endpoint to restrict access to only authenticated users. You also need to update your API calls by passing your authentication token. ### Enable Authenticated Requests to Strapi Content API Head over to ***Settings > Users & Permissions Plugin > Roles > Public*** then choose **Strapi-content**) under **permissions**. Select all allowed actions and click save. Then proceed to ***Settings > Users & Permissions Plugin > Roles >Authenticated***. Under **Strapi-content**, select all allowed actions or select the ones you want to utilize and save. ![image](https://hackmd.io/_uploads/HJaE8uXHC.png) Then proceed to **Settings API Tokens** and create an API token that will authenticate your requests. ![image](https://hackmd.io/_uploads/H1-UDuQrA.png) Make sure to save the token in a safe place as you won't be able to see it again when you exit the page. ### Autenticated API Request With The `fetch()` in Strapi To make an authenticated API call, you need to pass the authentication token in the request header. That's the only modification to the API calls you made earlier. Let's look at how to retrieve data from the secure endpoint. ```javascript // ./strapi_content_api.js async function getEntries() { const url = "http://localhost:1337/api/strapi-contents"; const token = "YOUR_TOKEN"; // Replace with your token try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, // Include the Authorization header }, }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || "Network response was not ok"); } const jsonResponse = await response.json(); const entries = jsonResponse.data; // Accessing the 'data' array console.log("Data retrieved:", entries); // retrieve specific collection fields /* const courseTitles = entries.map(entry => entry.CourseTitle); console.log('Course Titles:', courseTitles); // console.log('Data retrieved:', entries);*/ } catch (error) { console.error("Error:", error); } } getEntries(); ``` Update the other codes accordingly if you would like to secure your endpoint. Make sure you do not share the API token in public repositories. The best way is to store it in an environmental variable. You have by far been making basic calls. Check out [Guide on authenticating requests with the REST API](https://strapi.io/blog/guide-on-authenticating-requests-with-the-rest-api) to learn more. Let's take a look at more advanced case scenarios. ## Making More Advanced API Calls with `fetch()` in Strapi In this section, we will handle Strapi internals such as file uploads and downloads, pagination, and filtering, and finally how to populate related data. ### Pagination and Filtering with `fetch()` in Strapi When you have a very large dataset, retrieving all the data present in the content API endpoint might overwhelm the server or your frontend. To make the data manageable, you should retrieve the data in chunks through pages. Strapi provides two methods for pagination. **page-based pagination** and **offset-based** pagination. #### Page-based Pagination `page` here specifies the desired page number (starts from 1). Page-based pagination is more intuitive. You will often use it when dealing with paginated views in a UI. Here's how you can implement it: ```javascript // ./strapi_content_api.js // Function to retrieve paginated entries using page-based pagination async function getPageBasedEntries(page, pageSize) { const url = `http://localhost:1337/api/strapi-contents?pagination[page]=${page}&pagination[pageSize]=${pageSize}`; const token = "Your API token"; // Replace with your token console.log(`Fetching paginated entries from: ${url}`); try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, // Include the Authorization header }, }); if (!response.ok) { const errorData = await response.json(); console.error("Error details:", errorData); throw new Error(errorData.message || "Network response was not ok"); } const jsonResponse = await response.json(); const entries = jsonResponse.data; // Accessing the 'data' array console.log("Paginated Data retrieved:", entries); } catch (error) { console.error("Error occurred:", error.message); } } getPageBasedEntries(1, 10); // Retrieve the first page with 10 entries per page ``` The function above takes the `page` and `pageSize` as its parameters. The `page` represents the page number you want to retrieve and the `pagesize` represents the number of entries to fetch per page. The function constructs a URL string that points to the Strapi API endpoint including the query parameters for pagination. The function then uses the `fetch()` method to make a `GET` request to the constructed `URL`.If any errors occur during the process, they are caught and logged using `console.error`. #### Offset-based Pagination `Offset` indicates the number of entries to skip before starting the retrieval. Offset-based pagination is useful when you need more control over the number of entries skipped before fetching the data. Let's see how to implement it. ```javascript // ./strapi_content_api.js // Function to retrieve paginated entries using offset-based pagination async function getOffsetBasedEntries(start, limit) { const url = `http://localhost:1337/api/strapi-contents?pagination[start]=${start}&pagination[limit]=${limit}`; const token = "Your API token"; // Replace with your token console.log(`Fetching paginated entries from: ${url}`); try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, // Include the Authorization header }, }); if (!response.ok) { const errorData = await response.json(); console.error("Error details:", errorData); throw new Error(errorData.message || "Network response was not ok"); } const jsonResponse = await response.json(); const entries = jsonResponse.data; // Accessing the 'data' array console.log("Paginated Data retrieved:", entries); } catch (error) { console.error("Error occurred:", error.message); } } getOffsetBasedEntries(2, 10); // Retrieve entries starting from index 2, limit to 10 entries ``` The above function is very similar to the **Page-based Pagination** one. The only difference is that the above function takes start and limit parameters. The `start` parameter represents the starting index from which to retrieve entries. The `limit` represents the maximum number of entries to fetch. ### Filtering Data Based on Certain Criteria with The `fetch()` method in Strapi Sometimes, you want to retrieve data that meets certain criteria. Strapi supports retrieving specific subsets of data based on certain specifications. Let's see how we can implement this using code. ```javascript // ./strapi_content_api.js // Function to retrieve filtered entries async function getFilteredEntries(filterKey, filterValue) { const filterQuery = `filters[${filterKey}][$contains]=${filterValue}`; const url = `http://localhost:1337/api/strapi-contents?${filterQuery}`; const token = "YOUR_TOKEN"; // Replace with your token console.log(`Fetching filtered entries from: ${url}`); try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, // Include the Authorization header }, }); if (!response.ok) { const errorData = await response.json(); console.error("Error details:", errorData); throw new Error(errorData.message || "Network response was not ok"); } const jsonResponse = await response.json(); const entries = jsonResponse.data; // Accessing the 'data' array console.log("Filtered Data retrieved:", entries); } catch (error) { console.error("Error occurred:", error.message); } } // Example usage const filterKey = "CourseTitle"; const filterValue = "Strapi"; getFilteredEntries(filterKey, filterValue); ``` Strapi uses `filters` and `contains` parameters to filter data. `filters` specifies the field and `contains` specifies the string you are looking for in the field's entry. The above function retrieves the entries in which the `CourseTitle` contains the word `Strapi`. ![Strapi Filtering](https://hackmd.io/_uploads/By8jHMNS0.png) To have a look at all the parameters you can use during filtering, [take a look at this guide](https://docs.strapi.io/dev-docs/api/rest/filters-locale-publication). You can as well learn about [Demystifying Strapi's Populate & Filtering](https://strapi.io/blog/demystifying-strapi-s-populate-and-filtering). ### Populating Related Data With the `fetch()` Method in Strapi When working with relationships in your content types, you may need to fetch related data along with your main entries. Strapi's Content API makes this easy with the `populate` parameter. Go back to Strapi and create a new Collection type named `Review`. This will store the reviews for each course. Then add `review` and `relations` fields to the collection. The relation field will define the relationship between a course and a review. If you have difficulty doing so, refer to this [video about building data relations in Strapi](https://www.youtube.com/watch?v=gB8HRpIAE3o). The new collection should look like this. ![Reviews Collection](https://hackmd.io/_uploads/BJwpoMEB0.png) Go ahead and expose the collection as we did earlier and also create a new entry by reviewing a course. Let's see how to populate related data. ```javascript // ./strapi_content_api.js // Function to retrieve entries and populate related data async function getEntriesWithPopulate() { // Replace '*' with specific relations if needed const url = "http://localhost:1337/api/strapi-contents?populate=*"; const token = "Your token"; // Replace with your token console.log(`Fetching entries with populated data from: ${url}`); try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, // Include the Authorization header }, }); if (!response.ok) { const errorData = await response.json(); console.error("Error details:", errorData); throw new Error(errorData.message || "Network response was not ok"); } const jsonResponse = await response.json(); const entries = jsonResponse.data; // Accessing the 'data' array console.dir(entries, { depth: null }); } catch (error) { console.error("Error occurred:", error.message); } } getEntriesWithPopulate(); ``` The code above instructs the content API to populate all related data for the fetched entries. You can replace `*` with specific relations if needed For example if you have many relations and you want to only populate the review relation, you should pass `?review`. Notice in the above code you used `console.dir` to print the output instead of `console.log`. The reason is `console.log()` function in Node.js does not fully print nested objects beyond a certain depth. So to print the full response you use `console.dir()`. Here is how the response looks: ![016-related-data](https://hackmd.io/_uploads/ryjmNAhL0.png) In the response, you can see that in the first entry, there is a new attribute that contains the review. ## Uploading and Downloading files in Strapi Using `fetch()` So far you have been dealing with text data. This section will advance your knowledge by showing you how to handle media data. Create two more fields of type media in your `strapi-content` Collection type and name them `CourseLogo` and `CourseMaterials`. They will store your media data. Your collection should look this: ![Media Fields](https://hackmd.io/_uploads/By6rMu4rC.png) The Strapi Content API supports file uploads and downloads. ### Handling File Uploads To upload a file, create a `FormData` object containing the file data. Then, pass it to the `fetch()` method. To upload a file you will use the `POST` method of the `fetch()` method and associate it with the specific **field** and **entry**. You will need `fs` (File System) for working with file paths and `form-data` for creating multipart/form-data requests. ```javascript // ./strapi_content_api.js import fs from "fs"; import fetch from "node-fetch"; import FormData from "form-data"; // Function to upload file and link it to a specific entry async function uploadFileAndLink(filePath, refId, ref, field) { const url = "http://localhost:1337/api/upload"; const token = "your-token"; // Replace with your token const formData = new FormData(); formData.append("files", fs.createReadStream(filePath)); formData.append("refId", refId); formData.append("ref", ref); formData.append("field", field); try { const response = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${token}`, // Include the Authorization header }, body: formData, }); if (!response.ok) { // Handle non-JSON responses correctly const text = await response.text(); throw new Error(text); } const jsonResponse = await response.json(); console.log("File uploaded and linked successfully:", jsonResponse); return jsonResponse; } catch (error) { console.error("Error:", error); } } // Function to update a specific entry with unique files async function updateSpecificEntry(entryId, logoPath, materialsPaths) { try { // Upload course logo and link to the entry await uploadFileAndLink( logoPath, entryId, "api::strapi-content.strapi-content", "CourseLogo", ); // Upload course materials (e.g., PDFs) and link to the entry for (const material of materialsPaths) { await uploadFileAndLink( material, entryId, "api::strapi-content.strapi-content", "CourseMaterials", ); } } catch (error) { console.error("Error in updating entry with unique files:", error); } } // Run the function to update a specific entry const entryId = 4; // Specify the entry ID to update const logoPath = "logo-advanced-programming.jpg"; // Specify the path to the logo file const materialsPaths = ["material1.pdf"]; // Specify the paths to the material files updateSpecificEntry(entryId, logoPath, materialsPaths); ``` The above code contains two functions. The first function takes the following parameters during upload: * `refId`: This is the ID of the entry to which the file should be linked. * `ref`: This is the API endpoint reference for the entry type. * `field`: This is the field name in the entry where the file should be linked. * `filePath`: This is the path to the file that needs to be uploaded. It then creates a new `FormData` object to hold the `file` and the `metadata` required for uploading and linking the file. The function then uses `fetch()` to make a `POST` request to the constructed **URL**. It then sends the `FormData` object as the request body and handle the errors that might occur. The second function takes three parameters: `entryId`, `logoPath`, and `materialsPaths`. It is responsible for updating your entries with the media data. * `entryId`: This is the ID of the entry that needs to be updated. * `logoPath`: This is the path to the logo file that should be uploaded and linked to the entry. * `materialsPaths`: This is an array of paths to the course material files that should be uploaded and linked to the entry. The function calls the `uploadFileAndLink` function to upload the logo file and link it to the specified entry using the `CourseLogo` field. Then, it iterates over the `materialsPaths`array and calls the `uploadFileAndLink` function for each material file, linking them to the specified entry using the `CourseMaterials` field. If any errors occur during the file upload and linking process, they are caught and logged into the console. The screenshot below shows the JSON logged onto the console showing the uploaded files and their descriptions. ![image](https://hackmd.io/_uploads/S1vnS22IR.png) You can also check whether the files were uploaded directly in Strapi. To do so, proceed to the Strapi dashboard, and click on **Media Library**. Your uploads will be located in the **API Uploads** folder. ![image](https://hackmd.io/_uploads/SJgu7nn80.png) In the screenshot above, you can see that the two files were uploaded successfully. ### Handling File Downloads To download files follow these steps: 1. Retrieve the entry data to get the `URLs` of the files or photos. 1. Download the files using the `URLs`. ```javascript // ./strapi_content_api.js import fs from "fs"; import fetch from "node-fetch"; import path from "path"; import { fileURLToPath } from "url"; // Function to retrieve a specific content entry with populated media fields async function getEntry(entryId) { const url = `http://localhost:30080/api/strapi-contents/${entryId}?populate=*`; const token = "YOUR_AUTH_TOKEN"; // Replace with your token try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, // Include the Authorization header }, }); if (!response.ok) { throw new Error("Failed to fetch content entry"); } const jsonResponse = await response.json(); return jsonResponse.data; // Return the entry data } catch (error) { console.error("Error:", error); } } // Function to download a file from a given URL async function downloadFile(fileUrl, dest) { const response = await fetch(fileUrl); if (!response.ok) { throw new Error(`Failed to download file: ${response.statusText}`); } const fileStream = fs.createWriteStream(dest); await new Promise((resolve, reject) => { response.body.pipe(fileStream); response.body.on("error", reject); fileStream.on("finish", resolve); }); } // Function to download files linked to a specific entry async function downloadEntryFiles(entryId) { try { // Retrieve the specific entry data with populated media fields const entry = await getEntry(entryId); if (!entry) { console.log(`Entry ID ${entryId} not found.`); return; } // Determine the directory path for downloads const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Download CourseLogo const logo = entry.attributes.CourseLogo?.data; if (logo) { const logoUrl = `http://localhost:30080${logo.attributes.url}`; const logoDest = path.join(__dirname, path.basename(logo.attributes.url)); await downloadFile(logoUrl, logoDest); console.log(`Downloaded CourseLogo to ${logoDest}`); } // Download CourseMaterials const materials = entry.attributes.CourseMaterials?.data || []; for (const material of materials) { const materialUrl = `http://localhost:30080${material.attributes.url}`; const materialDest = path.join( __dirname, path.basename(material.attributes.url), ); await downloadFile(materialUrl, materialDest); console.log(`Downloaded CourseMaterial to ${materialDest}`); } } catch (error) { console.error("Error in downloading entry files:", error); } } // Run the function to download files for a specific entry const entryId = 4; // Specify the entry ID to download files for downloadEntryFiles(entryId); ``` The above code has three functions. The first function named `getEntry` fetches the entry details with populated media fields from the Strapi API. It uses a specified `entryId` and **authorization token**. The `downloadFile` function handles downloading a file from a given URL to a specified destination. The core function is the `downloadEntryFiles` function, it retrieves the entry data and iterates over the media fields (`CourseLogo` and `CourseMaterials`), constructing the URLs and file paths for each media file. It then calls `downloadFile` to save each file to the local directory. The code uses `import.meta.url` with `fileURLToPath` to determine the directory path for saving the downloaded files, ensuring compatibility with ES modules. Once you run the code, the downloaded files are saved at the same folder as your project folder. ## Conclusion You now have extensive knowledge of making API calls to Strapi's content API in different case scenarios using the `fetch()` API. However, there are a lot more ways in which you can advance the call's logic to handle more functionalities. Take a look at the following resources to advance your knowledge even further. ## Further Resources 1. https://docs.strapi.io/dev-docs/plugins/upload 1. https://docs.strapi.io/ 1. https://github.com/strapi/strapi 1. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch ## General Comments From Reviewer ### Mastering API Requests: Using Fetch with Strapi's Content API ### SEO Review - **Meta Description**: The meta description effectively summarizes the article and includes keywords like "fetch()" and "Strapi's Content API." Consider adding a call to action (e.g., "Learn more about using fetch() with Strapi's Content API.") to increase click-through rates. - **Keywords**: Relevant keywords are used throughout the article, such as "Fetch API," "Strapi," "HTTP requests," "create, retrieve, update, delete data," "authenticated API calls," and "advanced API calls." Ensure these keywords are naturally integrated into the content without keyword stuffing. - **Internal Links**: The article includes links to related content, such as the Strapi documentation and relevant guides. Consider adding links to other blog posts on Strapi website for improved internal linking and SEO. ### Grammar and Style Review - **Introduction**: The introduction is clear and sets the context for the article. Consider adding a brief overview of what Strapi is and why it's beneficial for managing content through API requests. - **Clarity**: The content is generally clear and easy to follow. Ensure technical terms are explained for readers who may not be familiar with them. ### Content Sections Review - **Introduction**: Provides a good overview of the Fetch API and its advantages. Consider adding a brief introduction to Strapi. - **Setting up Strapi**: Step-by-step instructions are clear and easy to follow. Screenshots are helpful. Consider breaking down this section into smaller sub-sections for better readability. - **Sending POST Requests with Fetch**: Provides a detailed explanation with code examples. Ensure the code formatting is consistent throughout. - **Making GET Requests with Fetch**: Clear explanation and example code. Consider adding a sub-section on handling errors in more detail. - **Updating and Deleting Entries with PUT and DELETE Requests**: Well-explained with examples. Ensure the code snippets are consistently formatted. - **Making Authenticated API Calls to the Strapi Contents API Endpoint**: Provides clear instructions and examples. Consider adding more details on securing API tokens. - **Making More Advanced API Calls**: Covers advanced use cases effectively. Ensure to include image results - **Conclusion**: Summarizes the key points well. Consider adding a call to action (e.g., inviting readers to try Strapi and Fetch API in their projects). ### Suggested Improvements 1. **Introduction to Strapi**: Add a brief introduction to Strapi, explaining what it is and its benefits. 2. **Call to Action**: Add calls to action in the meta description and conclusion to encourage readers to engage with the content. 3. **Sub-sections**: Break down longer sections into smaller sub-sections with clear headings for better readability. 4. **Code Formatting**: Ensure consistent code formatting throughout the article. Use syntax highlighting for better readability. 5. **Error Handling**: Add more details on handling errors in API requests, especially in the GET and POST sections. 6. **Security**: Provide more information on securing API tokens and handling sensitive data. ### SEO Keywords - Fetch API - Strapi - HTTP requests - Create data with fetch - Retrieve data with fetch - Update data with fetch - Delete data with fetch - Authenticated API calls - Advanced API calls - Strapi content API ### Final Notes - Ensure all links are functional and direct readers to the appropriate resources. - Please provide a Github Repo to your codes. -