Warning an error occurred while requesting the api strapi

# Error handling Strapi is natively handling errors with a standard format. There are 2 use cases for error handling: As a developer querying content through the REST or GraphQL APIs, you might receive errors in response to the requests. As a developer customizing the backend of your Strapi application, you could use controllers […]

Содержание

  1. # Error handling
  2. # Receiving errors
  3. # REST errors
  4. # GraphQL errors
  5. # Throwing errors
  6. # Controllers and middlewares
  7. # Services and models lifecycles
  8. # Policies
  9. # Default error classes
  10. An error occurred while requesting the API #12660
  11. Comments
  12. Bug report
  13. System
  14. Warning: An error occurred while requesting the API, only in localhost #14956
  15. Comments
  16. Bug report
  17. System information
  18. Describe the bug
  19. Steps to reproduce the behavior
  20. Expected behavior
  21. Screenshots
  22. Additional context

# Error handling

Strapi is natively handling errors with a standard format.

There are 2 use cases for error handling:

  • As a developer querying content through the REST or GraphQL APIs, you might receive errors in response to the requests.
  • As a developer customizing the backend of your Strapi application, you could use controllers and services to throw errors.

# Receiving errors

Errors are included in the response object with the error key and include information such as the HTTP status code, the name of the error, and additional information.

# REST errors

Errors thrown by the REST API are included in the response that has the following format:

# GraphQL errors

Errors thrown by the GraphQL API are included in the response that has the following format:

# Throwing errors

# Controllers and middlewares

The recommended way to throw errors when developing any custom logic with Strapi is to have the controller or middleware respond with the correct status and body.

This can be done by calling an error function on the context (i.e. ctx ). Available error functions are listed in the http-errors documentation

(opens new window) but their name should be lower camel-cased to be used by Strapi (e.g. badRequest ).

Error functions accept 2 parameters that correspond to the error.message and error.details attributes received by a developer querying the API:

  • the first parameter of the function is the error message
  • and the second one is the object that will be set as details in the response received

# Services and models lifecycles

Once you are working at a deeper layer than the controllers or middlewares there are dedicated error classes that can be used to throw errors. These classes are extensions of Node Error class

(opens new window) and are specifically targeted for certain use-cases.

These error classes are imported through the @strapi/utils package and can be called from several different layers. The following examples use the service layer but error classes are not just limited to services and model lifecycles. When throwing errors in the model lifecycle layer, it’s recommended to use the ApplicationError class so that proper error messages are shown in the admin panel.

See the default error classes section for more information on the error classes provided by Strapi.

This example shows wrapping a core service and doing a custom validation on the create method:

Example: Throwing an error in a model lifecycle

This example shows building a custom model lifecyle and being able to throw an error that stops the request and will return proper error messages to the admin panel. Generally you should only throw an error in beforeX lifecycles, not afterX lifecycles.

# Policies

Policies are a special type of middleware that are executed before a controller. They are used to check if the user is allowed to perform the action or not. If the user is not allowed to perform the action and a return false is used then a generic error will be thrown. As an alternative, you can throw a custom error message using a nested class extensions from the Strapi ForbiddenError class, ApplicationError class (see Default error classes for both classes), and finally the Node Error class

The PolicyError class is available from @strapi/utils package and accepts 2 parameters:

  • the first parameter of the function is the error message
  • (optional) the second parameter is the object that will be set as details in the response received; a best practice is to set a policy key with the name of the policy that threw the error.

Example: Throwing a PolicyError in a custom policy

This example shows building a custom policy that will throw a custom error message and stop the request.

# Default error classes

The default error classes are available from the @strapi/utils package and can be imported and used in your code. Any of the default error classes can be extended to create a custom error class. The custom error class can then be used in your code to throw errors.

The ApplicationError class is a generic error class for application errors and is generally recommended as the default error class. This class is specifically designed to throw proper error messages that the admin panel can read and show to the user. It accepts the following parameters:

Parameter Type Description Default
message string The error message An application error occured
details object Object to define additional details <>

The PaginationError class is a specific error class that is typically used when parsing the pagination information from REST, GraphQL, or the Entity Service. It accepts the following parameters:

Parameter Type Description Default
message string The error message Invalid pagination

The NotFoundError class is a generic error class for throwing 404 status code errors. It accepts the following parameters:

Parameter Type Description Default
message string The error message Entity not found

The ForbiddenError class is a specific error class used when a user either doesn’t provide any or the correct authentication credentials. It accepts the following parameters:

Parameter Type Description Default
message string The error message Forbidden access

The UnauthorizedError class is a specific error class used when a user doesn’t have the proper role or permissions to perform a specific action, but has properly authenticated. It accepts the following parameters:

Parameter Type Description Default
message string The error message Unauthorized

The PayloadTooLargeError class is a specific error class used when the incoming request body or attached files exceed the limits of the server. It accepts the following parameters:

Parameter Type Description Default
message string The error message Entity too large

The PolicyError class is a specific error designed to be used with route policies. The best practice recommendation is to ensure the name of the policy is passed in the details parameter. It accepts the following parameters:

Источник

An error occurred while requesting the API #12660

Bug report

i am trying to run Strapi on ubuntu 20.04, i am using nginx proxy, everything works normal until:

[2022-02-25 09:22:03.622] http: GET /admin (32 ms) 200
[2022-02-25 09:22:03.689] http: GET /admin/admin/runtime

main.ad201388.js (17 ms) 200
[2022-02-25 09:22:03.691] http: GET /admin/admin/main.de6c3b66.js (5 ms) 200
[2022-02-25 09:22:04.782] http: GET /admin/admin/project-type (12 ms) 200
[2022-02-25 09:22:04.784] http: GET /admin/admin/styled-components.browser.cjs.js.map (7 ms) 200
[2022-02-25 09:22:04.811] http: GET /admin/admin/5395.c175c167.chunk.js (7 ms) 200
[2022-02-25 09:22:04.814] http: GET /admin/admin/6882.f894575d.chunk.js (6 ms) 200
[2022-02-25 09:22:04.820] http: GET /admin/admin/6747.9679fd79.chunk.js (7 ms) 200
[2022-02-25 09:22:04.832] http: GET /admin/admin/2790.886abe9a.chunk.js (5 ms) 200
[2022-02-25 09:22:04.935] http: GET /admin/admin/fontawesome-css.855dec85.chunk.js (16 ms) 200
[2022-02-25 09:22:04.938] http: GET /admin/admin/en-json.a16a4d46.chunk.js (6 ms) 200
[2022-02-25 09:22:04.942] http: GET /admin/admin/fr-json.d987ee1f.chunk.js (8 ms) 200
[2022-02-25 09:22:04.966] http: GET /admin/admin/fontawesome-css-all.1e6a3452.chunk.js (6 ms) 200
[2022-02-25 09:22:05.000] http: GET /admin/admin/email-translation-fr-json.307e50e2.chunk.js (17 ms) 200
[2022-02-25 09:22:05.005] http: GET /admin/admin/content-type-builder-translation-en-json.c6f777c5.chunk.js (16 ms) 200
[2022-02-25 09:22:05.010] http: GET /admin/admin/content-type-builder-translation-fr-json.0a1971e4.chunk.js (14 ms) 200
[2022-02-25 09:22:05.015] http: GET /admin/admin/email-translation-en-json.9a812408.chunk.js (13 ms) 200
[2022-02-25 09:22:05.018] http: GET /admin/admin/upload-translation-en-json.ed15e832.chunk.js (11 ms) 200
[2022-02-25 09:22:05.022] http: GET /admin/admin/upload-translation-fr-json.a1922c08.chunk.js (10 ms) 200
[2022-02-25 09:22:05.032] http: GET /admin/admin/i18n-translation-en-json.f020e3b6.chunk.js (6 ms) 200
[2022-02-25 09:22:05.045] http: GET /admin/admin/users-permissions-translation-en-json.20ebcf72.chunk.js (10 ms) 200
[2022-02-25 09:22:05.052] http: GET /admin/admin/i18n-translation-fr-json.c97d6db7.chunk.js (14 ms) 200
[2022-02-25 09:22:05.055] http: GET /admin/admin/users-permissions-translation-fr-json.f0263de2.chunk.js (12 ms) 200
[2022-02-25 09:22:05.057] http: GET /admin/admin/fontawesome-js.37deade1.chunk.js (10 ms) 200
[2022-02-25 09:22:05.101] http: GET /admin/admin/b997a22a2e0b87ef1fa2.ico (3 ms) 200
[2022-02-25 09:25:39.215] http: GET /admin/admin/en-json.a16a4d46.chunk.js (4 ms) 200
[2022-02-25 09:25:39.217] http: GET /admin/admin/fr-json.d987ee1f.chunk.js (5 ms) 200
[2022-02-25 09:25:39.245] http: GET /admin/admin/fontawesome-css-all.1e6a3452.chunk.js (14 ms) 200
[2022-02-25 09:25:39.263] http: GET /admin/admin/content-type-builder-translation-en-json.c6f777c5.chunk.js (14 ms) 200
[2022-02-25 09:25:39.273] http: GET /admin/admin/content-type-builder-translation-fr-json.0a1971e4.chunk.js (19 ms) 200
[2022-02-25 09:25:39.279] http: GET /admin/admin/email-translation-en-json.9a812408.chunk.js (18 ms) 200
[2022-02-25 09:25:39.281] http: GET /admin/admin/upload-translation-en-json.ed15e832.chunk.js (10 ms) 200
[2022-02-25 09:25:39.284] http: GET /admin/admin/email-translation-fr-json.307e50e2.chunk.js (8 ms) 200
[2022-02-25 09:25:39.299] http: GET /admin/admin/upload-translation-fr-json.a1922c08.chunk.js (12 ms) 200
[2022-02-25 09:25:39.316] http: GET /admin/admin/i18n-translation-en-json.f020e3b6.chunk.js (22 ms) 200
[2022-02-25 09:25:39.324] http: GET /admin/admin/i18n-translation-fr-json.c97d6db7.chunk.js (22 ms) 200
[2022-02-25 09:25:39.330] http: GET /admin/admin/users-permissions-translation-en-json.20ebcf72.chunk.js (26 ms) 200
[2022-02-25 09:25:39.332] http: GET /admin/admin/users-permissions-translation-fr-json.f0263de2.chunk.js (14 ms) 200
[2022-02-25 09:25:39.334] http: GET /admin/admin/fontawesome-js.37deade1.chunk.js (8 ms) 200
[2022-02-25 09:25:39.452] http: GET /admin/admin/b997a22a2e0b87ef1fa2.ico (32 ms) 200
[2022-02-25 09:25:39.464] http: GET /admin/admin/init (66 ms) 200
[2022-02-25 09:25:39.618] http: GET /admin/admin/cropper-css.f325a989.chunk.js (8 ms) 200

then this error occurs

Same for dev and production builds.
api routes are working fine.

System

  • Node.js version: 16
  • NPM version: 16.14.0
  • Strapi version: 4.1.1 (4.0.7 same result)
  • Database: postgres
  • Operating system: ubuntu 20.04

The text was updated successfully, but these errors were encountered:

Источник

Warning: An error occurred while requesting the API, only in localhost #14956

Bug report

Admin server doesn’t start on localhost.

System information

  • Node.js version: 16.18.1 (LTS)
  • NPM version: 8.19.2
  • Strapi version: 4.5.0
  • Database: Postgre
  • Operating system: Windows 11

Describe the bug

Admin server doesn’t start on localhost but is running normal on production. Infinite loading loop. I can’t understand why in develop mode the logs are showing the slug admin/admin/init duplicated for example when in production all is ok.

Steps to reproduce the behavior

  1. clone project
  2. npm i or yarn
  3. npm run build or yarn build
  4. npm run develop or yarn develop

Expected behavior

Run the project in development mode

Screenshots

.env (with the next variables too, APP_KEYS, API_TOKEN_SALT, ADMIN_JWT_SECRET, JWT_SECRET)

server.js

admin.js

Additional context

I tried deleting the .cache build node_modules serveral times, build again but I got the same error.

The text was updated successfully, but these errors were encountered:

Can you please try upgrading to v4.5.4? We have released a few hotfixes I believe that should solve this issue (also please do delete your lock file and node_modules when doing this upgrade)

Thank you for reporting this bug, however we are unable to reproduce the issue you described given the information we have on hand. Can you please create a fresh project that you are able to reproduce the issue in, provide clear steps to reproduce this issue, and either upload this fresh project to a new GitHub repo or compress it into a .zip and upload it on this issue?

We would greatly appreciate your assistance with this, by working in a fresh project it will cut out any possible variables that might be unrelated.
Please note that issues labeled with status: can not reproduce will be closed in 14 days if there is no activity.

As we have not received any new or updated information to reproduce this issue in the last 14 days we are marking this issue as closed. Should you have new information please feel free to respond and we will consider reopening it.

If anyone else have updated information for this issue, please open up a new bug report and simply reference this closed bug report so that we can get any new information you may have. If you have questions please refer to the contributor’s guide on opening issues.

Источник

Еще десять лет назад при создании порталов и блогов стандартом де факто признавались классические CMS написанные на PHP или Perl. Технологии не стоят на месте и развитие Javascript, HTML и повсеместное внедрение API принесло с собой новые технологии создания сайтов.

Развитие Vue.js, который по сути является meta-React – технологией, впитав в себя самое лучшее, при этом упростив всё сложное и выкинув ненужное, новый фреймворк стал дальнейшим развитием всего, что впервые появилось в React.js.

Существует огромное количество библиотек, расширяющих возможности Vue.js. С помощью этого инструмента можно создавать, как простые лендинги, так и сложные информационные порталы или веб-приложения.

Strapi — https://strapi.io – фреймворк для управления контентом т.е. CMS. В отличии от Joomla или WordPress у него нет фронтэнда — графического представления информации хранящейся в базе данных, т.е. сайта, на который может зайти пользователь, а есть только панель администратора.

Все данные эта CMS отдает через API в формате JSON. Таким образом, во-первых, сам сайт может быть размещен на отдельном сервере, во-вторых появляется возможность горизонтального масштабирования.

Не говоря о том, что снижаются накладные расходы на вычисления, так как не используется PHP, а запросы API возвращают только текстовую информацию, которая загружается и отображается с помощью Vue.js, зачастую вообще без перезагрузки страницы!

Сегодня мы рассмотрим установку Strapi на сервер Alt Linux.

В рамках этого цикла я не буду рассматривать создание самого сайта на Vue.js. Эта тема сама по себе крайне обширна, и, чтобы не распылять силы я планирую каждый раз давать вам готовый проект для тестирования API. Скачать его вы сможете с Github.

Установка nodejs

Для работы нам понадобиться Node.js. Его установку мы уже рассматривали в этой статье.

Установка Strapi

Создадим папку для будущего сайта:

sudo mkdir -p /var/strapi/site1
sudo chown -R user /var/strapi
sudo chgrp -R user /var/strapi

Где user имя текущего пользователя.

Запустим установку Strapi: 

cd /var/strapi
npx create-strapi-app site1 --quickstart

После окончания установки в консоли появится сообщение: 

Building your admin UI with development configuration...
Admin UI built successfully
Project information

┌────────────────────────────────────────────────────────────────────────
│ Time               │ Mon Dec 19 2022 18:58:49 GMT+1100 (Магадан, ста… │
│ Launched in        │ 7750 ms                                          │
│ Environment        │ development                                      │
│ Process PID        │ 452156                                           │
│ Version            │ 4.5.4 (node v14.21.1)                            │
│ Edition            │ Community                                        │
└────────────────────────────────────────────────────────────────────────

 Actions available

One more thing...
Create your first administrator 💻 by going to the administration panel at:

┌──────────────────────────────
│ http://localhost:1337/admin │
└──────────────────────────────

А теперь нажимаем Ctrl+C и это важно, запускаем: 

npm run build

Ждем окончания сборки и запускаем: 

npm run develop

Если этого не сделать, вы будете постоянно получать ошибку: 

Warning: An error occurred while requesting the API

Создание первого администратора

Откроем в браузере адрес: 

 http://192.168.0.6:1337/

Где 192.168.0.6 – адрес нашего сервера

Откроется окно:

2022-12-19_14-23-20.png

Нажмем Create the first administrator

Откроется форма регистрации первого администратора, заполним форму:

2022-12-19_14-24-48.png

Нажмем, Let’s start

Откроется рабочее пространство Strapi

2022-12-19_14-27-06.png

Мы установили Strapi.

Заключение

Сегодня мы рассмотрели установку Strapi в Alt Linux:

Установили Node.js;

Создали папки для сайта;

Установили необходимые пакеты с помощью npx;

Запустили сборку и сервер разработчика;

Подключились к панели администратора;

Создали администратора;

Вошли в рабочее пространство администратора Strapi.

В следующей части мы создадим необходимые элементы нашего будущего блога на API.

Bug report

Required System information

  • Node.js version: v16.15.
  • NPM version: 8.5.5
  • Strapi version: 4.2.0-beta.3
  • Database: mariadb
  • Operating system: linux debian

Note that the generated project use TypeScript.

Describe the bug

Hi, when I hit the admin panel localhost:1337/admin in the browser, I got a blank page, with an infinite purple loader, and a popup opens with the message

Warning: An error occurred while requesting the API

In the console, there are some error logs

Content Security Policy: Page settings prevented an inline ("script-src") resource from loading.

I run it through docker, and running it with the command npm start (aka strapi start) after a successful build.
This is my Dockerfile

FROM node:16-alpine
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/app
COPY ./ .

RUN npm ci
ENV PATH /opt/app/node_modules/.bin:$PATH

# build strapi app and start it
RUN npm run clean
RUN npm run build
RUN npm prune --production
EXPOSE 1337
CMD ["npm", "start"]

and the docker-compose.yml I use for build

version: "3.9"
services:
  strapi:
    container_name: strapi
    build:
      context: .
      dockerfile: .docker/Dockerfile
    image: custom-strapi
    restart: unless-stopped
    env_file: .env
    environment:
      NODE_ENV: ${STRAPI_NODE_ENV}
      HOST: ${STRAPI_HOST}
      PORT: ${STRAPI_PORT}
      STRAPI_URL: ${STRAPI_URL}
      APP_KEYS: ${STRAPI_APP_KEYS}
      API_TOKEN_SALT: ${STRAPI_API_TOKEN_SALT}
      ADMIN_JWT_SECRET: ${STRAPI_ADMIN_JWT_SECRET}
      USERS_PERM_JWT_SECRET: ${STRAPI_USERS_PERM_JWT_SECRET}
      DATABASE_CLIENT: ${DATABASE_CLIENT}
      DATABASE_HOST: ${DATABASE_HOST}
      DATABASE_PORT: ${DATABASE_PORT}
      DATABASE_USER: ${DATABASE_USER}
      DATABASE_PASSWORD: ${DATABASE_PASSWORD}
      DATABASE_NAME: ${DATABASE_NAME}
      STRAPI_TELEMETRY_DISABLED: ${STRAPI_TELEMETRY_DISABLED}
    ports:
      - "1337:1337"
    networks:
      - strapi
    depends_on:
      - strapi-db

Finally, this is my config/server.ts

export default ({ env }) => ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  app: {
    keys: env.array('APP_KEYS'),
  },
  url: env('STRAPI_URL'), // http://localhost:1337
});

Strapi app does not emit errors logs, just the received request as expected

[2022-05-30 21:16:59.678] http: GET /admin (2 ms) 200

NOTE: the same app works well locally, running it via npm run build && npm start or npm run develop, on both development and production env.

Expected behavior

The admin panel should just work as expected.

Screenshots

image

Additional context

There is no additional

In this tutorial, you will build a News app using Strapi and React.js. You will use the Strapi i18n plugin to translate the backend content from one language to another. Strapi will be used to build the backend and provide the API endpoints, and React.js we will use to build our frontend part.

What is Strapi?

Strapi is an open-source headless content management system based on Node.js. Strapi provides us with a nice UI to build our APIs. We can build APIs in the form of collections. A collection is a web resource that has endpoints — GET, DELETE, POST, and PUT. These endpoints perform a separate action to the web resource.

Strapi provides the web resource endpoints without us writing any server code. The web resources content can be added to Strapi via its admin panel, also using the API.

For example, if we create a movies collection from the admin panel. Strapi will provide us with the following endpoints:

  • /movies GET: This gets all the movies in the backend.
  • /movies/:id GET: This gets a movie from the backend.
  • /movies/:id PUT: This edits a movie.
  • /movies POST: This adds a new movie to the backend.
  • /movies/:id DELETE: This removes a movie from the backend.

So with just a collection, we get the above API endpoints. Strapi is self-hosted. It has an in-built server that serves the collections we create. Also, a Strapi project can be hosted in the cloud.

Strapi supports relational databases such as MySQL, SQLite, PostgreSQL, etc. With Strapi, we only have to build the frontend (mobile, web, or desktop) and use HTTP to fetch the data from the Strapi backend.

The i18n Strapi Plugin

Content internationalization is very popular these days. You can build an app with content displayed in English, but there are chances that a Chinese or Spanish user. You wouldn’t want the language to be a barrier that stops users from accessing your app.

With content internationalization, you can translate the content of your application from one language to another. You can translate from English to Chinese, from English to Spanish, French to German, etc.

The Strapi i18n plugin allows users to create different localized/language versions of their API content. It helps developers build different language versions of their projects by fetching the content based on the language/locale of the user.

Prerequisites

Here are some things you should know, have, or do before continuing this tutorial.

Knowledge Requirements

To follow along, you need to have some basic knowledge of:

  • Javascript: A scripting language for web pages.
  • React: A Javascript library for building user interfaces.
  • Node.js: A Javascript runtime environment for the backend.
  • Shell (Bash): Command language interpreter.
  • Git: Distributed version control system.

Software Requirements

In this tutorial, we will need to install some tools if not already installed:

  • Operating System (any of the following are supported):

    • Ubuntu >= 18.04 (LTS-Only). I used Ubuntu 20.04 LTS for this tutorial.
    • Debian >= 9.x
    • CentOS/RHEL >= 8
    • macOS Mojave or newer (ARM not supported)
    • Windows 10 or 11
    • Docker — docker repo
  • Node: Both Strapi and Reactjs are Node-based tools and frameworks respectively. Supported versions are v14 or v16. Odd-number versions of Node are not supported (e.g. v13 or v15). To download the Nodejs binary, get it from the Download | Nodejs page. I used Node v16.14.2.

  • npm: This is a Node package manager. This comes bundled with the Nodejs binary. I used npm v8.5.0.

  • yarn (Optional): An alternative to npm. Check Installation | Yarn for installation instructions.

  • Postman: An API platform to test our app’s API endpoints. Download the desktop version or sign up for an account to use the web version.

  • Code Editor: Any editor of your choice. I used VSCode.

  • Web Browser: Any Chromium-based browser. I used Google Chrome.

  • Git: For version control. Download Git from the Git — Downloads page.

  • Git Bash: (for Windows users). The tutorial uses Unix shell commands. Git Bash can run UNIX shell commands in Windows. Git Bash ships with your Git download.

Hardware Requirements

  • At least 1 CPU core (Highly recommended, at least 2).
  • At least 2GB of RAM (Moderately recommended).
  • Minimum required storage space recommended by your OS or 32 GB of free space.

Set up Strapi Backend

The first thing we need to do is set up the Strapi backend.

NOTE: If you prefer testing the ready-made app, you can clone the Github repo.

1. Scaffold a Strapi Project

In your terminal, create a folder to contain the entire source code for this tutorial. Name it newsapp, move into it, and initialize git for source control.

    $ mkdir newsapp
    $ cd newsapp
    /newsapp $ git init

Enter fullscreen mode

Exit fullscreen mode

Create a Strapi project named newsapp-api.

    /newsapp $ npx create-strapi-app newsapp-api --quickstart
    # or
    /newsapp $ yarn create strapi-app newsapp-api --quickstart

Enter fullscreen mode

Exit fullscreen mode

This will create a Strapi project in the newsapp-api folder. The --quickstart flag sets up your Strapi app with an SQLite database. After executing the command, enter y if prompted to proceed with the installation. Wait for Strapi and all its dependencies to be installed in the newsapp-api folder.

After installation, your app should start automatically. Stop the server by pressing Ctrl plus C on your keyboard. Your project folder newsapp after installing Strapi should have a structure similar to this:

    /newsapp $ tree -L 2 -a
    .
    ├── newsapp-api
    │   ├── build
    │   ├── .cache
    │   ├── config
    │   ├── data
    │   ├── database
    │   ├── .editorconfig
    │   ├── .env
    │   ├── .env.example
    │   ├── .eslintignore
    │   ├── .eslintrc
    │   ├── favicon.ico
    │   ├── .gitignore
    │   ├── node_modules
    │   ├── package.json
    │   ├── public
    │   ├── README.md
    │   ├── src
    │   ├── .strapi-updater.json
    │   ├── .tmp
    │   └── yarn.lock
    ├── .git
    ├── LICENSE
    └── README.md

Enter fullscreen mode

Exit fullscreen mode

Move inside the Strapi install folder.

    /newsapp $ cd newsapp-api

Enter fullscreen mode

Exit fullscreen mode

2. Install Strapi i18n plugin

This Strapi i18n can be installed:

  1. From the Terminal
  2. From the Marketplace

According to the installation docs,, the Internationalization plugin is installed by default on all Strapi applications running on version 3.6.0 or higher. For lower versions, a migration is needed (see Update Strapi version), as well as a manual installation of the plugin.

This tutorial uses Strapi version 4.3.2 which ships with the i18n plugin by default.

3. Register an Admin User

Start the Strapi server

    /newsapp/newsapp-api $ npm run develop
    # OR
    /newsapp/newsapp-api $ yarn develop

Enter fullscreen mode

Exit fullscreen mode

npm run develop starts your Strapi project development server in watch mode. In watch mode changes in your Strapi project will trigger a server restart.

Strapi will start its server at http://localhost:1337, and load its admin panel at http://localhost:1337/admin.

Strapi Admin Registration

NOTE:
If you visit http://localhost:1337/admin and see “Strapi: Warning: an error occurred while requesting the API”.

Stop the server, Ctrl + C. Change the host IP address in ./newsapp/newsapp-api/config/server.js from 0.0.0.0 to 127.0.0.1 . If you are stuck, check out this gist.

Fill in your details, and click on the LET’S START button. This makes Strapi register your details and shows the admin dashboard.

Strapi Admin Dashboard

Now, we will begin creating our collections.

4. Build the Strapi Collections

We are building a news website, a single news item will have this model:

    newspost {
        title
        body
        writtenBy
        imageUrl
    }

Enter fullscreen mode

Exit fullscreen mode

  • The title is the title of the news.
  • The body is the content of the news.
  • The writtenBy is the author of the news.
  • The imageUrl is the header image of the news.

Strapi will provide us with these endpoints:

  • /newsposts GET: This endpoint will retrieve all the news content.
  • /newsposts/:id GET: This endpoint will retrieve a single news post based on the :id.
  • /newsposts POST: This endpoint will create a new news item.
  • /newsposts/:id DELETE: This endpoint will delete a particular news item.
  • /newsposts/:id PUT: This endpoint will edit a particular news item based on the :id

On the admin page, click on the Create your first Content type button. This will take you to the Content-Type Builder plugin page. Click on + Create a collection type to display the Create a collection type modal. Enter newspost for the Display name and leave the API ID (Singular) and API ID (Plural) as is.

Create a collection type - Basic Settings

We need to enable localization for this collection, so move to the top-right of the modal and click on the Advanced Settings tab. Scroll down and tick the Enable localization for this Content-Type checkbox. This will allow you to have content in different locales.

Create a collection type - Enable localization

Click Continue and a modal appears with field options for your collection type. Select Text.

Select Text field for Newspost collection

In the Add new Text field modal, enter title for Name, leave Type as Short text and click on + Add another field.

Add new Text field named title

Add Text fields named writtenBy and imageUrl. The type should be Short text. Next, add a Rich text field named body and click Finish. This loads a view of the Newspost collection type with the four fields you just created. Click Save and when the server restarts Newspost will be listed under COLLECTION TYPES in the left sidebar.

Newspost collection type

When the server restarted, the database for your Strapi app was updated with the newspost collection type. New files in ./newsapp/newsapp-api/src/api are created to provide the business logic for the newspost API.

    /newsapp/newsapp-api $ cd src
    /newsapp/newsapp-api/src $ tree
    .
    ├── admin
    │   ├── app.example.js
    │   └── webpack.config.example.js
    ├── api
    │   └── newspost
    │       ├── content-types
    │       │   └── newspost
    │       │       └── schema.json
    │       ├── controllers
    │       │   └── newspost.js
    │       ├── routes
    │       │   └── newspost.js
    │       └── services
    │           └── newspost.js
    ├── extensions
    └── index.js

Enter fullscreen mode

Exit fullscreen mode

5. Adding Languages

With localization enabled, the content will be seeded in the database for each language or locale. English is by default the selected language.

Click Settings on the sidebar. Under GLOBAL SETTINGS, click on Internationalization.

Settings: Internationalization Plugin Menu

English (en) is the default locale/language. Let’s add the French locale. Click on the + Add a locale button on the top-right. A modal shows up. Click on the Locales dropdown. All locales supported by Strapi will cascade down. Select French (France) (fr-FR). Click on ✔️ Save.

Add French (France) (fr-FR) locale

See that the French (France) (fr-FR) locale is added to your list of locales for your Newsposts collection.

List of locales

Our app now supports both English and French languages.

6. Seeding our database

Now, we will add content to our database. Click on Content Manager on the sidebar. Under COLLECTION TYPES select newspost.

Notice that any newspost entry you make will be English (en) locale by default. Since you have added the French (France) (fr-FR) locale, clicking on the dropdown box will display the French (France) (fr-FR) locale listed below the English (en) locale.

Locale options in Content Manager for newspost

Locales added in the Internationalization section will appear in the dropdown. You can select them to add content for that language. So, if you select the French (France) (fr-FR), the content locale will then be for French (France) (fr-FR).

But for now, let’s add news content for the English (en) locale. Click on + Create new entry in the top right corner.

Enter the following data for your entry:

    title -> Nepal's Minister sworn into office
    imageUrl -> https://thehimalayantimes.com/uploads/imported_images/wp-content/uploads/2020/10/Ministers-Sworn-in.jpg
    writtenBy -> John Doe
    body -> Type anything here

Enter fullscreen mode

Exit fullscreen mode

Click on Save, then Publish to publish your article. You can add many more news items as you deem fit.

Let’s add two more:

    title -> Angular 12 released.
    imageUrl -> https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Angular_full_color_logo.svg/1280px-Angular_full_color_logo.svg.png
    writtenBy -> M. Hry
    body -> Type anything here

Enter fullscreen mode

Exit fullscreen mode

Click on Save then Publish. Add this:

    title -> React v17 released
    imageUrl -> https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/1920px-React-icon.svg.png
    writtenBy -> Dan Tierry
    body -> Type anything here

Enter fullscreen mode

Exit fullscreen mode

Click on Save, then Publish. We are done adding news posts for the English (en) locale. The list of your news posts should be similar to this:

List of News posts

7. Add News Content for the French Locale

Click on the English (en) dropdown box and select French (France) (fr-FR).
Select French locale

The page will reload:
News posts for French locale

There is no data for French (France) (fr-FR) yet, but we can add them. Here, we will add the French translation of our English version.

For the first data on the French version, we will add this:

    title -> Le ministre du Népal a prêté serment
    imageUrl -> https://thehimalayantimes.com/uploads/imported_images/wp-content/uploads/2020/10/Ministers-Sworn-in.jpg
    writtenBy -> John Doe
    body -> Type_any_thing_here.

Enter fullscreen mode

Exit fullscreen mode

Click on + Create new entry and add the above content. Click on Save and then on Publish. Now, do this for the second and third posts:

    title -> Angular 12 est sorti.
    imageUrl -> https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Angular_full_color_logo.svg/1280px-Angular_full_color_logo.svg.png
    writtenBy -> M. Hry
    body -> Type_any_thing_here.

Enter fullscreen mode

Exit fullscreen mode

    title -> Sortie de React v17
    imageUrl -> https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/1920px-React-icon.svg.png
    writtenBy -> Dan Tierry
    body -> Type_any_thing_here.

Enter fullscreen mode

Exit fullscreen mode

News posts for the French (France) fr-FR locale are ready.

List of French news posts

8. Open Access for the Newsposts

Now, only authenticated users can access our endpoints. Let’s make the endpoints to be accessible to the public.

To do that:

Testing Our Strapi Backend Endpoints

Let’s test the endpoints. Open Postman. If you don’t have it already, you can download it from here.

1. Get All Newsposts

Create a new collection so we can organize our requests. Add a request to the collection. Set the Request type to GET in the input box type http://localhost:1337/api/newsposts and click on Send.

Newsposts API endpoint view in Postman

See that only the Newsposts for the English (en) locale are returned because the default locale is set to English (en). To get the French translation, we add the query, ?locale=fr-FR to the URL and send. The fr-FR is the standard language code for the French language.

Type http://localhost:1337/api/newsposts?locale=fr-FR in the input box and click Send. The result will give the French locale news posts as seen below.

French News posts API Endpoint

Notice that both newsposts in both English and French have unique ids. So if we fetch a News with its id, we don’t have to add the locale= param. It will fetch the content of the news with no effect on the locale.

2. Adding a News Item

To add a news item to a particular locale, we will have to pass the language code of the locale in the body payload. Change the request method to POST. Select Body, choose raw then add the following JSON data for the new news post.

    {
        "data": {
            "title": "Nouvelles fonctionnalités de Vue js et changements de rupture - Présentation de Vue 3",
            "body": "Avec la sortie d'une nouvelle version majeure du noyau, toutes les autres parties du cadre devaient progresser ensemble. Nous devions également fournir une voie de migration pour les utilisateurs de Vue 2. Il s'agissait d'une entreprise massive pour une équipe dirigée par la communauté comme Vue. Lorsque le noyau de Vue 3 était prêt, la plupart des autres parties du framework étaient soit en version bêta, soit en attente de mise à jour. Nous avons décidé d'aller de l'avant et de publier le noyau afin que les premiers utilisateurs, les auteurs de bibliothèques et les frameworks de niveau supérieur puissent commencer à construire avec lui pendant que nous travaillions sur le reste du framework.nn Dans le même temps, nous avons conservé Vue 2 comme valeur par défaut pour la documentation et les installations npm. En effet, nous savions que pour de nombreux utilisateurs, Vue 2 offrait toujours une expérience plus cohérente et plus complète jusqu'à ce que d'autres parties de Vue 3 soient affinées.",
            "writtenBy": "CJ",
            "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1920px-Vue.js_Logo_2.svg.png",
            "locale": "fr-FR"
        }
    }

Enter fullscreen mode

Exit fullscreen mode

Click Send and wait for a response from the server. If successful, you should get the following response:

    {
        "data": {
            "id": 8,
            "attributes": {
                "title": "Nouvelles fonctionnalités de Vue js et changements de rupture - Présentation de Vue 3",
                "body": "Avec la sortie d'une nouvelle version majeure du noyau, toutes les autres parties du cadre devaient progresser ensemble. Nous devions également fournir une voie de migration pour les utilisateurs de Vue 2. Il s'agissait d'une entreprise massive pour une équipe dirigée par la communauté comme Vue. Lorsque le noyau de Vue 3 était prêt, la plupart des autres parties du framework étaient soit en version bêta, soit en attente de mise à jour. Nous avons décidé d'aller de l'avant et de publier le noyau afin que les premiers utilisateurs, les auteurs de bibliothèques et les frameworks de niveau supérieur puissent commencer à construire avec lui pendant que nous travaillions sur le reste du framework.nn Dans le même temps, nous avons conservé Vue 2 comme valeur par défaut pour la documentation et les installations npm. En effet, nous savions que pour de nombreux utilisateurs, Vue 2 offrait toujours une expérience plus cohérente et plus complète jusqu'à ce que d'autres parties de Vue 3 soient affinées.",
                "writtenBy": "CJ",
                "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1920px-Vue.js_Logo_2.svg.png",
                "createdAt": "2022-08-30T19:39:30.199Z",
                "updatedAt": "2022-08-30T19:39:30.199Z",
                "publishedAt": "2022-08-30T19:39:30.194Z",
                "locale": "fr-FR"
            }
        },
        "meta": {}
    }

Enter fullscreen mode

Exit fullscreen mode

POST Request Payload

The final confirmation for the successful news post addition is it is visible in the list of French locale news posts. Navigate to the French (France) fr-FR locale list of news posts in the Content Manager and you will see it. See we set fr-FR to the locale prop so Strapi knows this goes into the French locale.

Newly added news post in French list

We are done with our Strapi backend; let’s build the frontend to see how the two meet.

Set up React App Frontend

Let’s move to the next stage, which is setting up the React app frontend.

1. Scaffold React App

Now, you will build the frontend of your app.
Change the directory to the root of your project folder newsapp:

    /newsapp/newsapp-api $ cd ..

Enter fullscreen mode

Exit fullscreen mode

First, make sure you have the create-react-app tool installed on your machine. If not, you can install it globally by running this command:

    /newsapp $ npm i create-react-app -g

Enter fullscreen mode

Exit fullscreen mode

Test the installation by running the command:

    /newsapp $ create-react-app --version

Enter fullscreen mode

Exit fullscreen mode

From here, scaffold the React project. Create a newsapp-strapi React project by running the following command:

    /newsapp $ create-react-app newsapp-strapi

Enter fullscreen mode

Exit fullscreen mode

Move into the folder:

    /newsapp $ cd newsapp-strapi

Enter fullscreen mode

Exit fullscreen mode

Install the axios module, which you will use for querying Strapi endpoints:

    /newsapp/newsapp-strapi $ npm i axios

Enter fullscreen mode

Exit fullscreen mode

Next, you will need routes in our app. Install the react-router-dom:

    /newsapp/newsapp-strapi $ npm i react-router-dom

Enter fullscreen mode

Exit fullscreen mode

Start the server:

    /newsapp/newsapp-strapi $ npm run start

Enter fullscreen mode

Exit fullscreen mode

Go to your browser and navigate to http://localhost:3000 to see the React frontend for your app.

2. Build the Components

LET’S BEGIN WITH THE SETUP.
Your news app will have two routes:

  • /news: This route will render all the news in our app.
  • /newsview/:id: This route will render a particular news item. The :id will be the id attribute of the news item.

Your final app will look like this:

News app Home page

Add News post modal

This displays a single news post.

Full view of Single News post

Let’s break our app into components.

  • Header: This will hold the header section of your app.
  • NewsList: This component will be a page component. It will be rendered when you navigate to the /news route. It will display the list of news. It is a smart component.
  • NewsCard: This component will display an overview of a news item. It will be rendered by the NewsList component.
  • NewsView: This component is a page component, it displays the full details of a news item. It is the news page, where users will read a piece of particular news.
  • AddNewsDialog: This is a dialog component, it is where news is added to your app.
    Header, NewsList and NewsCard Component

AddNewsDialog Component

NewsView Component

The page components will be in the pages folder while the presentational components will be in the components folder.

Create two folders pages and components inside the src folder.

    /newsapp/newsapp-strapi $ mkdir src/pages && mkdir src/components

Enter fullscreen mode

Exit fullscreen mode

Create Header, AddNewsDialog, and NewsCard components inside the components folder. Move inside the components folder:

    /newsapp/newsapp-strapi $ cd src/components

Enter fullscreen mode

Exit fullscreen mode

Create a folder named AddNewsDialog and create an index.js file inside it.

    /newsapp/newsapp-strapi/src/components $ mkdir AddNewsDialog && touch AddNewsDialog/index.js

Enter fullscreen mode

Exit fullscreen mode

Create a folder named Header. Add index.js and Header.css to the Header folder.

    /newsapp/newsapp-strapi/src/components $ mkdir Header && touch Header/index.js && touch Header/Header.css

Enter fullscreen mode

Exit fullscreen mode

Do the same for the NewsCard component.

    /newsapp/newsapp-strapi/src/components $ mkdir NewsCard && touch NewsCard/index.js && touch NewsCard/NewsCard.css

Enter fullscreen mode

Exit fullscreen mode

Create the page components. Switch to the pages folder.

    /newsapp/newsapp-strapi/src/components $ cd /newsapp/newsapp-strapi/src/pages

Enter fullscreen mode

Exit fullscreen mode

Add the NewsList component.

    /newsapp/newsapp-strapi/src/pages $ mkdir NewsList && touch NewsList/index.js && touch NewsList/NewsList.css

Enter fullscreen mode

Exit fullscreen mode

Add the NewsView component.

    /newsapp/newsapp-strapi/src/pages $ mkdir NewsView && touch NewsView/index.js && touch NewsView/NewsView.css

Enter fullscreen mode

Exit fullscreen mode

Open App.js in the src folder. Clear the content and paste the below code:

    /* /newsapp/newsapp-strapi/src/App.js */

    import "./App.css";
    import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
    import Header from "./components/Header";
    import NewsList from "./pages/NewsList";
    import NewsView from "./pages/NewsView";

    function App() {
      return (
        <>
          <Header />
          <div className="container">
            <main className="main">
              <BrowserRouter>
                <Switch>
                  <Route path="/news">
                    <NewsList />
                  </Route>
                  <Route path="/newsview/:id">
                    <NewsView />
                  </Route>
                  <Route exact path="/">
                    <Redirect to="/news" />
                  </Route>
                  <Route path="*">
                    <NewsList />
                  </Route>{" "}
                </Switch>
              </BrowserRouter>
            </main>
          </div>
        </>
      );
    }
    export default App;

Enter fullscreen mode

Exit fullscreen mode

We set up two routes: /news and newsview/:id. The first route will render the NewsList component while the last route will render the NewsView component.

We used React Router to set up routing here. The BrowserRouter component initializes the routing system, the Switch component wraps the dynamic routes, and the Route configures the specific routes and wraps the component the route that will render. Anything else outside the BrowserRouter component will render on every page. See that we render the Header component outside it, so the header appears on all our pages.

Next, clear the code in App.css and save the file.

HEADER COMPONENT

Add the below code to index.js in the Header component folder.

    // /newsapp/newsapp-strapi/src/components/Header/index.js

    import "./Header.css";

    export default function Header() {
      return (
        <section className="header">
          <div className="headerName">News24</div>
        </section>
      );
    }

Enter fullscreen mode

Exit fullscreen mode

Just a simple UI that displays the text “News24”. Add some styling to your header, using the Header.css file:

    /* /newsapp/newsapp-strapi/src/components/Header/Header.css */

    .header {
      height: 54px;
      background-color: black;
      color: white;
      display: flex;
      align-items: center;
      padding: 10px;
      font-family: sans-serif;
      /*width: 100%;*/
      padding-left: 27%;
    }
    .headerName {
      font-size: 1.8em;
    }

Enter fullscreen mode

Exit fullscreen mode

NEWSLIST COMPONENT

Insert the following code in the index.js file for the NewsList component:

    // /newsapp/newsapp-strapi/src/pages/NewsList/index.js

    import "./NewsList.css";
    import NewsCard from "./../../components/NewsCard";
    import { useEffect, useState } from "react";
    import axios from "axios";
    import AddNewsDialog from "../../components/AddNewsDialog";

    export default function NewsList() {
        const [newsList, setNewsList] = useState([]);
        const [locale, setLocale] = useState("en");
        const [showModal, setShowModal] = useState(false);

        useEffect(() => {
            async function fetchNews() {
                const data = await axios.get(
                    "http://localhost:1337/api/newsposts?_locale=" + locale
                );
                setNewsList([...data?.data]);
            }
            fetchNews();
        }, [locale]);

        function setLang() {
            setLocale(window.locales.value);
        }

        function showAddNewsDialog() {
            setShowModal(!showModal);
        }

        return (
            <div className="newslist">
                <div className="newslistbreadcrumb">
                    <div className="newslisttitle">
                        <h3>World News</h3>
                    </div>
                    <div style={{ display: "flex", alignItems: "center" }}>
                        <div style={{ marginRight: "4px" }}>
                            <button onClick={showAddNewsDialog}>Add News</button>
                        </div>
                        <div>
                            <select name="locales" id="locales" onChange={setLang}>
                                <option value="en">English</option>
                                <option value="fr-FR">French</option>
                            </select>
                        </div>
                    </div>
                </div>
                <div>
                    {newsList?.map((newsItem, i) => (
                        <NewsCard newsItem={newsItem} key={i} />
                    ))}
                </div>
                {showModal ? <AddNewsDialog closeModal={showAddNewsDialog} /> : null}
            </div>
        );
    }

Enter fullscreen mode

Exit fullscreen mode

Here we set up three states. The first holds the list of news. The second holds the current locale/language. It is by default set to English en. The last holds the modal visibility state.

The useEffect() function loads the news whenever the component mounts from the http://localhost:1337/api/newsposts?_locale=" endpoint. The current locale is added to it based on the selected locale. This enables of internationalization of the news app. You read news in both English and French.

The result sets the newsList state. This causes the component to render and the UI displays the news in a long list. We added a locale dependency to the useEffect so the callback runs when the locale state changes.

The functions setLang and showAddNewsDialog sets the selected locale to the locale state and toggles the showModal state respectively.

Let’s analyze the UI code.

See that we added an Add News button that when clicked the AddNewsDialog modal shows up. We also added a language select element, this is where we can select the language the news will be rendered in. We have «English» and «French», this is so because we have internationalization on our backend for «English» and «French». When any of the options is clicked, the language version of the app is loaded.

Add the styling. Open NewsList.css and paste the below code:

    /* /newsapp/newsapp-strapi/src/pages/NewsList/NewsList.css */

    .newslistbreadcrumb {
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 1px solid darkgray;
    }
    .newslisttitle {
      margin-left: 12px;
    }
    .newslisttitle h3 {
      color: rgb(107 107 107);
      font-weight: 500;
    }

Enter fullscreen mode

Exit fullscreen mode

NEWWSVIEW COMPONENT
This page component renders the news selected from the NewsList component. Insert the following code to the index.js file for the NewsView page:

    /* /newsapp/newsapp-strapi/src/pages/NewsView/index.js */

    import "./NewsView.css";
    import { useParams } from "react-router-dom";
    import axios from "axios";
    import { useEffect, useState } from "react";

    export default function NewsView() {
        let { id } = useParams();
        const [news, setNews] = useState();

        useEffect(() => {
            async function getNews() {
                const data = await axios.get("http://localhost:1337/api/newsposts/" + id);
                setNews(data?.data);
            }
            getNews();
        }, [id]);

        async function deleteNews() {
            if (window.confirm("Do you want to delete this news?")) {
                await axios.delete("http://localhost:1337/api/newsposts/" + id);
                window.history.pushState(null, "", "/news");
                window.location.reload();
            }
        }
        return (
            <div className="newsview">
                <div
                    className="newsviewimg"
                    style={{ backgroundImage: `url(${news?.imageUrl})` }}
                ></div>
                <div>
                    <div className="newsviewtitlesection">
                        <div className="newsviewtitle">
                            <h1>{news?.title}</h1>
                        </div>
                        <div className="newsviewdetails">
                            <span style={{ flex: "1", color: "rgb(99 98 98)" }}>
                                Written By: <span>{news?.writtenBy}</span>
                            </span>
                            <span style={{ flex: "1", color: "rgb(99 98 98)" }}>
                                Date: <span>{news?.created_at}</span>
                            </span>
                            <span>
                                <button className="btn-danger" onClick={deleteNews}>
                                    Delete
                                </button>
                            </span>
                        </div>
                    </div>
                    <div className="newsviewbody">{news?.body}</div>
                </div>
            </div>
        );
    }

Enter fullscreen mode

Exit fullscreen mode

We start by using the useParams hook to get the :id param and the value is stored in the id variable. We set a state to hold the current news being viewed. The useEffect loads the news details from the endpoint http://localhost:1337/api/newsposts/ + id and set the result to the news state.

The deleteNews function deletes this news by performing a DELETE HTTP request to the endpoint http://localhost:1337/api/newsposts/" + id. Then, navigate back to the /news page. This component’s UI displays the news in its entirety.

Add styling. Open NewsView.css and paste the below code:

    /* /newsapp/newsapp-strapi/src/pages/NewsView/NewsView.css */

    .newsview {
      margin-top: 7px;
    }
    .newsviewimg {
      background-color: darkgray;
      background-repeat: no-repeat;
      background-size: cover;
      background-position: center;
      height: 200px;
    }
    .newsviewdetails {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .newsviewtitlesection {
      margin-bottom: 20px;
    }
    .newsviewtitle h1 {
      margin-bottom: 6px;
    }
    .newsviewbody {
      font-size: large;
    }
    .newsviewbody::first-letter {
      font-weight: 700;
      font-size: 4em;
      line-height: 0.83;
      float: left;
      margin-right: 7px;
      margin-bottom: 4px;
    }
    .newsviewbody {
      clear: left;
      font-size: 21px;
      line-height: 1.58;
      letter-spacing: -0.003em;
    }

Enter fullscreen mode

Exit fullscreen mode

We used the ::first-select pseudoselector here .newsviewbody::first-letter to make the first letter in the news body appear larger 😁. That’s a great UI appeal just like what Medium.com does.

NEWSCARD COMPONENT

This component is rendered by the NewsList to display a list of news posts in the app. Insert the following code to the index.js file for the NewsCard component:

    /* /newsapp/newsapp-strapi/src/components/NewsCard/index.js */

    import { Link } from "react-router-dom";
    import "./NewsCard.css";

    export default function NewsCard({ newsItem }) {
      const { title, body, imageUrl, id } = newsItem;
      const synopsis = body.slice(0, 150);
      return (
        <Link to={"/newsview/" + id}>
          <div className="newscard">
            <div
              className="newscardimg"
              style={{ backgroundImage: `url(${imageUrl})` }}
            ></div>
            <div>
              <div className="newscardtitle">
                <h1>{title}</h1>
              </div>
              <div>
                <span>{synopsis}</span>
              </div>
              <div></div>
            </div>
          </div>
        </Link>
      );
    }

Enter fullscreen mode

Exit fullscreen mode

The newsItem is destructured from the component props, and then the news properties body, imageUrl, title, and id are destructured from it. Then, we generated a synopsis of the news from the body, this is just a snippet of the whole news. We did this by slicing off 150 characters from the whole news in the body and then displaying it.

See that we set a link to the full news by wrapping the div#newscard in the Link component with a link to newsview/ + id. This will load the NewsView component with the id param set to the param in the id variable.

Add styling to the NewsCard. Open the NewsCard.css and paste the below code:

    /* /newsapp/newsapp-strapi/src/components/NewsCard/NewsCard.css */

    .newscard {
      /*background-color: white;*/
      padding: 8px;
      /*box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
      transition: 0.3s;*/
      border-radius: 4px;
      margin: 8px;
      cursor: pointer;
      display: flex;
    }
    .newscardimg {
      width: 146px;
      height: 146px;
      background-color: darkgray;
      background-repeat: no-repeat;
      background-size: cover;
      background-position: center;
      margin-right: 9px;
      flex: 1 100%;
    }
    .newscardtitle {
      flex: 1 100%;
    }
    .newscardtitle h1 {
      margin-top: 0;
      margin-bottom: 1px;
    }

Enter fullscreen mode

Exit fullscreen mode

AddNewsDialog COMPONENT

Add the below code to index.js for the AddNewsDialog component:

    /* /newsapp/newsapp-strapi/src/components/AddNewsDialog/index.js */

    import { useState } from "react";
    import axios from "axios";

    export default function AddNewsDialog({ closeModal }) {
        const [disable, setDisable] = useState(false);
        async function saveNews() {
            const title = window.newsTitle.value;
            const imageUrl = window.newsImageUrl.value;
            const writtenBy = window.newsWrittenBy.value;
            const body = window.newsBody.value;
            const lang = window.addNewsLocales.value;

            setDisable(true);
            await axios.post("http://localhost:1337/api/newsposts", {
                title,
                imageUrl,
                writtenBy,
                body,
                locale: lang,
            });
            window.location.reload();
            setDisable(false);
        }

        return (
            <div className="modal">
                <div className="modal-backdrop" onClick={closeModal}></div>
                <div className="modal-content">
                    <div className="modal-header">
                        <h3>Add News</h3>
                        <span
                            style={{ padding: "10px", cursor: "pointer" }}
                            onClick={closeModal}
                        >
                            X
                        </span>
                    </div>
                    <div className="modal-body content">
                        <div style={{ display: "flex", flexWrap: "wrap" }}>
                            <div className="inputField">
                                <div className="label">
                                    <label>Title</label>
                                </div>
                                <div>
                                    <input id="newsTitle" type="text" />
                                </div>
                            </div>
                            <div className="inputField">
                                <div className="label">
                                    <label>Lang</label>
                                </div>
                                <div>
                                    <select name="addNewsLocales" id="addNewsLocales">
                                        <option value="en">English</option>
                                        <option value="fr-FR">French</option>
                                    </select>
                                </div>
                            </div>
                            <div className="inputField">
                                <div className="label">
                                    <label>ImageUrl</label>
                                </div>
                                <div>
                                    <input id="newsImageUrl" type="text" />
                                </div>
                            </div>
                            <div className="inputField">
                                <div className="label">
                                    <label>Written By</label>
                                </div>
                                <div>
                                    <input id="newsWrittenBy" type="text" />
                                </div>
                            </div>
                            <div className="inputField" style={{ flex: "2 1 100%" }}>
                                <div className="label">
                                    <label>Body</label>
                                </div>
                                <div>
                                    <textarea
                                        id="newsBody"
                                        style={{ width: "100%", height: "200px" }}
                                    ></textarea>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div className="modal-footer">
                        <button
                            disabled={disable}
                            className="btn-danger"
                            onClick={closeModal}
                        >
                            Cancel
                        </button>
                        <button disabled={disable} className="btn" onClick={saveNews}>
                            Save
                        </button>
                    </div>
                </div>
            </div>
        );
    }

Enter fullscreen mode

Exit fullscreen mode

We have a state that holds the disabling state of buttons. The UI displays input boxes and a text area where we enter our news info. Then, there is a Lang select element where we can select the language. Here, we hardcoded our two locales “English” and “French”, so we are bound with two languages here. It was done this way just to show you how you can toggle between languages from your frontend. A better solution will be to fetch the locales for this collection and dynamically list them in the select element.

Now, when adding your locales to the option element, the locale code is very important. Omitting any part of it will make it fetch the wrong language version or not work at all. For example, the French locale code is fr-FR, adding something like fr will not make it work. So we should be very careful when hardcoding locale code, or better still fetching the locales is the best option.

The “Save” button calls the saveNews function. The saveNews function picks the title, imageUrl, writtenBy, body, and lang values from the input boxes, text area, and select element, and disables the buttons. It then calls the http://localhost:1337/api/newsposts endpoint, passing the picked values as payload in the POST body. See that the language type for the news is set in the payload’s locale property. Finally, the page is reloaded and the newly added news post will be displayed on the news page.

3. Add Styling

Add global styles in index.css:

    /* /newsapp/newsapp-strapi/src/index.css */

    body {
      margin: 0;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
        "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
        "Helvetica Neue", sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      background-color: rgba(234, 238, 243, 1);
    }
    code {
      font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
        monospace;
    }
    button {
      height: 30px;
      padding: 0px 15px 2px;
      font-weight: 400;
      font-size: 1rem;
      line-height: normal;
      border-radius: 2px;
      cursor: pointer;
      outline: 0px;
      background-color: rgb(0, 126, 255);
      border: 1px solid rgb(0, 126, 255);
      color: rgb(255, 255, 255);
      text-align: center;
    }
    .btn-danger {
      background-color: rgb(195 18 18);
      border: 1px solid rgb(195 18 18);
    }
    .container {
      min-height: 100vh;
      /*padding: 0 0.5rem; */
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background-color: rgba(234, 238, 243, 1);
    }
    .main {
      /*padding: 5rem 0;*/
      flex: 1;
      display: flex;
      flex-direction: column;
      width: 46%;
      /*justify-content: center;
      align-items: center;*/
    }
    .modal {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      z-index: 1000;
      font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
    }
    .modal-backdrop {
      opacity: 0.5;
      width: inherit;
      height: inherit;
      background-color: grey;
      position: fixed;
    }
    .modal-body {
      padding: 5px;
      padding-top: 15px;
      padding-bottom: 15px;
    }
    .modal-footer {
      padding: 15px 5px;
      display: flex;
      justify-content: space-between;
    }
    .modal-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .modal-header h3 {
      margin: 0;
    }
    .modal-content {
      background-color: white;
      z-index: 1;
      padding: 10px;
      margin-top: 10px;
      width: 520px;
      box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 0px 24px 38px 3px rgba(0, 0, 0, 0.14),
        0px 9px 46px 8px rgba(0, 0, 0, 0.12);
      border-radius: 4px;
    }
    input[type="text"] {
      width: 100%;
      /*height: 3.4rem;*/
      padding: 9px;
      font-weight: 400;
      /*font-size: 1.3rem;*/
      cursor: text;
      outline: 0px;
      border: 1px solid rgb(227, 233, 243);
      border-radius: 2px;
      color: rgb(51, 55, 64);
      background-color: transparent;
      box-sizing: border-box;
    }
    .label {
      padding: 4px 0;
      font-size: small;
      color: rgb(51, 55, 64);
    }
    .content {
      display: flex;
      flex-wrap: wrap;
      flex-direction: column;
    }
    .inputField {
      margin: 3px 7px;
      flex: 1 40%;
    }
    .disable {
      opacity: 0.5;
      cursor: not-allowed;
    }
    a[href] {
      text-decoration: none;
      color: black;
    }
    a:visited {
      color: black;
    }

Enter fullscreen mode

Exit fullscreen mode

Test the App

We begin testing the app by making sure the backend server and the frontend are running.

    /newsapp/newsapp-strapi $ cd /newsapp/newsapp-api && yarn develop
    /newsapp/newsapp-api $ cd /newsapp/newsapp-strapi && npm run start

Enter fullscreen mode

Exit fullscreen mode

You will start by adding a news item. In your browser go to http://localhost:3000/news page and click on the Add News button. On the modal that shows up type in the data:

    title -> Minnesota National Guard deployed after protests over the police killing of a man during a traffic stop
    imageUrl -> https://logos-world.net/wp-content/uploads/2020/11/CNN-Logo-700x394.png
    Body -> (CNN) -- Hundreds of people __TRUNC__t," Walz tweeted.
    writtenBy -> CNN

Enter fullscreen mode

Exit fullscreen mode

Add News post in test mode

Click on Save.

List of News posts

The news post is added. Click on it to confirm if it is readable.

Newly added News post

Add the French (fr-FR) version. Go back to http://localhost:3000/news page. Click on Add News. This time change the language to French.

Add the French version of the news post:

    title -> La Garde nationale du Minnesota déployée après des manifestations contre le meurtre par la police d'un homme lors d'un arrêt de la circulation
    imageUrl -> https://logos-world.net/wp-content/uploads/2020/11/CNN-Logo-700x394.png
    Body -> (CNN) Des __TRUNC__es forces de l'ordre", a tweeté Walz.
    writtenBy -> CNN

Enter fullscreen mode

Exit fullscreen mode

Click on Save. Select the French language:

Sample

See the news in French, click on it.
Sample

Finally, test deleting the news posts.

Click on the Delete button and on the OK button on the confirm dialog that shows up. See the news is gone.

Conclusion

We learned how to use the Strapi i18n plugin to add internationalization to our Strapi API endpoints, and it is quite simple to use.

We started by learning what Strapi does and how it makes building APIs highly efficient. Next, we introduced the i18n plugin, and from there, we created a Strapi project to demonstrate how to use the i18n plugin in a Strapi project.

Next, we built a news app in React.js and showed how we could support multiple languages in a Reactjs app using Strapi as a backend with the help of the i18n plugin.

View the source code here.

Понравилась статья? Поделить с друзьями:
  • Warning an error occurred while opening a codec
  • Warning an error occurred while loading the project file the file is an unsupported format
  • Warning an error occurred while loading the project file sony vegas
  • Warning an error occurred during the current operation
  • Warning 217 loose indentation как исправить