In this comprehensive guide, you’ll build a React.js CRUD app using Redux Toolkit and RTK Query hooks. In brief, we’ll create RTK Query hooks that React will use to perform CRUD operations against a REST API.
What is RTK Query? RTK Query is a powerful server-state management library built on top of the Redux Toolkit core. It makes data fetching and caching in Redux projects a breeze and it also provides a powerful toolset to define API interface layers for an app.
To make the Redux Toolkit CRUD app interact with a RESTful API, I’ll provide step-by-step instructions in one of the sections to help you set up the backend API built with Django in minutes. I’ll also provide links for Deno, Node.js, and FastAPI.
More practice:
- CRUD RESTful API Server with Python, FastAPI, and MongoDB
- Node.js, Express, TypeORM, PostgreSQL: CRUD Rest API
- Build CRUD RESTful API Server with Golang, Gin, and MongoDB
- Next.js Full-Stack App with React Query, and GraphQL-CodeGen
- Build Full-Stack tRPC CRUD Application with Node.js, and React.js
- GraphQL CRUD API with Next.js, MongoDB, and TypeGraphQL

Prerequisites
This course assumes that you have:
- Basic knowledge of React.js.
- Basic understanding of API designs and CRUD architecture.
- The latest or LTS version of Node.js installed on your machine.
- Knowledge of Redux is strongly recommended since we will be using Redux Toolkit and RTK Query.
This course doesn’t require the following:
- TypeScript knowledge assuming you are already comfortable with JavaScript.
- Redux Toolkit and RTK Query knowledge, as they’ll be explained in this course.
Run the React Redux Toolkit App Locally
- Download the latest version of Node.js from https://nodejs.org/ and install the Yarn package manager globally with
npm i -g yarn
. - Download or clone the Redux Toolkit CRUD project from https://github.com/wpcodevo/react-rtkquery-crud-app and open it with an IDE.
- Open the integrated terminal in the root directory and run
yarn
oryarn install
to install the project’s dependencies. - In the console of the root directory, run
yarn dev
to start the Vite dev server - Visit http://localhost:3000/ in a new tab to see the Redux Toolkit CRUD app. Once the page has fully loaded, perform the CRUD operations against the Django API by interacting with the elements on the page.
Note: When you open the React app onhttp://127.0.0.1:3000
, you might get a site can’t be reached or a CORS error. This is because the Django server is configured to accept requests from only http://localhost:3000/.
Run a Django API with the React App
For a complete guide on how to build the Django CRUD API see the post Build CRUD API with Django REST framework. However, you can follow these steps to quickly spin up the Django API.
- Navigate to https://www.python.org/downloads/ to install Python.
- Open https://github.com/wpcodevo/Django_Crud_Project and download or clone the Django CRUD API project. After that, open the source code in an IDE or text editor.
- Execute this command in the console of the root directory to create a virtual environment.
- Windows OS –
python -m venv venv
- Mac or Linux OS –
python3 -m venv venv
- Windows OS –
- After the new virtual environment has been generated, you might be prompted by your IDE to activate it in the current workspace folder. Click “Yes” to activate it.
Note: To activate the virtual environment in the terminal, close the previously opened terminal and open a new one.
If for any reason your IDE or text editor didn’t prompt you to activate the new virtual environment, execute the command below in the console of the root directory to manually activate it.- Windows OS (Command Prompt ) –
venv\Scripts\activate.bat
. - Windows OS (Git Bash) –
venv/Scripts/activate.bat
. - Mac or Linux OS –
source venv/bin/activate
- Windows OS (Command Prompt ) –
- Install the project’s dependencies with
pip install -r requirements.txt
- Now run
python manage.py migrate
to migrate the database schema to the SQLite database. - Start the Django dev server by running
python manage.py runserver
- Interact with the Django API from the React app or open any API testing tool like Postman or Thunder Client VS Code extension to test the endpoints.
You can find a step-by-step implementation of the CRUD API in Node.js, Deno, and FastAPI from the following tutorials.
- Build a CRUD App with FastAPI and SQLAlchemy
- Build a CRUD App with FastAPI and PyMongo
- Build CRUD API with Deno
- Build CRUD API with Node.js
Setup the React Project with Tailwind CSS
Typically, we generate a React.js project with Create React App, but the set-up process can take an enormous amount of time since over 140 MB of dependencies has to be installed first. Create React App works well for small projects but as the project size increases, its performance decreases.
Vite on the other hand addresses these issues by leveraging ES Modules since all modern browsers have support for ES Modules. Also, the Vite scaffolding tool only installs 31 MB of dependencies to bootstrap a range of project types. At the time of writing this article, Vite supports React, Preact, Vue, Lit, Svelte, and even vanilla JavaScript.
Now let’s come back to the main focus of this section. We’ll bootstrap a React.js project with the Vite scaffolding tool and configure the project to use Tailwind CSS for styling.
Scaffold the React Project
First things first, open a terminal in your desktop directory or any convenient location on your machine and run this command to set up the React project.
This will create a directory named react-rtkquery-crud-app
and install the vite-create
binary from the NPM repository. After that, you will be prompted to select the type of framework. Since we are dealing with Redux Toolkit, select React as the framework type and choose TypeScript as the variant.
Once the project has been generated, run yarn
or yarn install
to install all the required dependencies. After the installation is complete, open the project in an IDE or text editor.
Now open the package.json file and replace the dev script with:
package.json
This will instruct Vite to start the dev server on port 3000 instead of the default port 5173.
Add Tailwind CSS
Now let’s add Tailwind CSS to the project. To do that, install the tailwind CSS library and its peer dependencies:
Generate the postcss.config.cjs
and tailwind.config.cjs
files with this command:
Open the newly-created tailwind.config.cjs
file and replace its content with the following tailwind CSS configurations:
tailwind.config.cjs
We added Poppins font to the theme.fontFamily
section, defined some custom colors, and provided paths to the template files.
Now open the src/index.css
file and replace its content with the following tailwind CSS directives, Poppins font import, and global CSS styles.
src/index.css
Create the CRUD API Slice with RTK Query
Redux Toolkit comes with RTK Query that can be used to create API slices to handle fetch requests and caching. We’ll utilize RTK Query to create an API slice that has five hooks. These hooks will allow us to perform the CRUD operations by making HTTP requests to the CRUD endpoints on a backend API.
To begin, open a terminal in the root folder and install these dependencies:
@reduxjs/toolkit
– Includes a set of utilities to simplify Redux developmentreact-redux
– An official React binding for Reduxnprogress
– A slim progress bar for Ajax’y applications@types/node
– Contains type definitions for Node.js@types/nprogress
– Contains type definitions for Nprogress
Since we chose TypeScript as the variant in the project scaffolding process, let’s define some TypeScript types to help us type the API responses and requests. To do this, go into the src directory and create a redux folder. After that, create a types.ts
file in the redux folder and add the following types.
src/redux/types.ts
With that out of the way, let’s define the API slice to describe how RTK Query should retrieve and send data to the endpoints on the backend API. As a rule of thumb, the Redux Toolkit team recommends we create one API slice per base URL.
To give visual feedback to the user when a request is in flight, we’ll use the nprogress
library to display a thin progress bar to indicate that the request is been processed by the backend API.
Create a noteAPI.ts
file in the src/redux directory and add the following RTK Query hooks. Typically, we create the API slices in an api
folder but since this project is small, we can skip that step.
src/redux/noteAPI.ts
useCreateNoteMutation
– This hook will fire an HTTP POST request to add a new note item to the database.useDeleteNoteMutation
– This hook will find a note item by ID and instruct the API to delete the found record from the database.useUpdateNoteMutation
– This hook will find a note item by ID and update its fields based on the data provided in the request body.useGetAllNotesQuery
– This hook will retrieve a paginated list of note items from the backend API.
Add the CRUD API Slice to the Redux Store
Now let’s create the Redux store and register the API slice using setupListeners()
and configureStore()
utility functions. The API slice contains an auto-generated Redux slice reducer and a custom middleware that manages subscription lifetimes. Both of these need to be added to the Redux store:
src/redux/store.ts
The configureStore()
function does a lot behind the scene — it automatically connects the app to Redux DevTools and adds redux-thunk
middleware by default to handle asynchronous operations.
To enable refetchOnFocus
and refetchOnReconnect
behaviors in the app, we passed the dispatch method available on the store to the setupListeners()
function.
Create Reusable React Components
In this section, you’ll create reusable components and style them with tailwind CSS. To begin, install the tailwind-merge
library that will allow us to efficiently merge Tailwind CSS classes in JS without style conflicts.
Now let’s create a Spinner component. Go into the src directory and create a components folder. Within the src/components folder, create a Spinner.tsx
file and add the following code snippets.
src/components/Spinner.tsx
Here, you’ll create a button that will display the Spinner component we created above. Create a LoadingButton.tsx
file in the src/components folder and add the TSX code below.
src/components/LoadingButton.tsx
Next, let’s create a modal component with React portals. Using React portals will allow us to render the popup component outside the React hierarchy tree without comprising the parent-child relationship between components.
So, create a note.modal.tsx
file in the src/components directory and paste the code below into the file.
src/components/note.modal.tsx
Now let’s create an element for the portal. Open the index.html
file and create a div with a note-modal
ID below the root div. React will render the content of the modal into this div when the modal is active.
At this point, let’s add the icon and nprogress
external stylesheets to the index.html file. To do that, open the index.html file and replace its content with the following markup.
index.html
Implement the CRUD Functionalities
Now that we have the RTK Query hooks and reusable components, let’s create four React components to implement the CRUD functionalities. But before that, open your terminal and install these dependencies.
date-fns
– Has utility functions for manipulating JavaScript dates.react-toastify
– Display alert notificationsreact-hook-form
– Form validation libraryzod
– A schema validation library@hookform/resolvers
– A validation resolver for React-Hook-Form
CREATE Operation
Now that you’ve installed the required dependencies, let’s write the logic for the CREATE functionality. This component will have a form with title and content input fields. The form validation will be handled by React-Hook-Form and the validation rules will be defined with the Zod library.
The validation schema created with Zod will be passed to React-Hook-Form’s useForm()
hook via the resolver property. Using our own validation schema will give us absolute control over the validation and how error messages are displayed on the form.
To perform the CREATE operation, we’ll leverage the useCreateNoteMutation()
hook derived from the RTK Query API slice. So, when the form is submitted and there are no validation errors, the onSubmitHandler() method will be called which will in turn evoke the createNote()
mutation to submit the form data to the backend API.
src/components/notes/create.note.tsx
After the createNote()
mutation has been evoked, RTK Query will add the form data to the request body and fire a POST request to the /api/notes
endpoint. Once the request is in flight, the NProgress.start()
function will be called to display a thin progress bar indicating that the request is being processed by the backend API.
If the mutation resolves successfully, the server-state cache will be invalidated which will cause RTK Query to re-fetch the most current list of items from the API.
After RTK Query has updated the cache with the list of items, React will re-render the DOM to display the newly-created item in the UI.
UPDATE Operation
Now let’s create a component to handle the UPDATE operation. This component will have a form for updating the fields of an item in the database. The form will have a title and content input fields.
To edit the fields of a note item, we’ll utilize the useUpdateNoteMutation()
hook derived from the RTK Query API slice. When the form is submitted and is valid, the updateNote()
mutation will be evoked to submit the form data to the backend API.
src/components/notes/update.note.tsx
RTK Query will add the form data to the request body and append the item’s ID to the request URL before making the PATCH request to the /api/notes/:noteId
endpoint.
Once the mutation resolves successfully, the cache belonging to the updated item will be invalidated which will cause RTK Query to fetch the newly-updated item from the API.
After the cache has been updated, React will re-render the DOM to reflect the changes that were made in the note item.
DELETE Operation
Here, let’s implement the DELETE functionality. To do this, we’ll create a note item component which will have buttons for triggering the UPDATE and DELETE operations.
To delete a note item in the database, we’ll leverage the useDeleteNoteMutation()
hook derived from the noteAPI
slice. When the Delete button is clicked, we’ll prompt the user to confirm the action or cancel it. If the user confirms his action, the deleteNote()
mutation will be evoked.
src/components/notes/note.component.tsx
RTK Query will then append the ID of the item to be deleted to the request URL and make a DELETE request to the /api/notes/:noteId
endpoint. If the mutation succeeds, the cache will be invalidated which will make RTK Query fetch the most current list of note items from the database. After that, React will re-render the DOM to remove the deleted item from the UI.
READ Operation
The last CRUD operation to implement is the READ functionality. To do this, we’ll use the useGetAllNotesQuery()
hook derived from the noteAPI
slice. This hook will be evoked to fetch a paginated list of note items from the API when the component mounts. In addition, the hook will re-run when the window gains focus or the internet is reconnected.
src/App.tsx
Test the React CRUD App
Now that we have created the CRUD components, let’s start the Vite dev server and test the React CRUD app with the backend API. So, run yarn dev
to start the development server.
Perform the CREATE Functionality
To add a new note item to the database, click on the plus (+) icon or the “Add new note” button to display the “Create Note” modal popup. Enter the title and content and click the Create Note button to submit the form.
Once the form has been submitted, React-Hook-Form will validate the fields against the rules defined in the Zod schema. If the form is valid, the useCreateNoteMutation()
hook will be evoked to submit the form data to the backend API.

The backend API will then validate the request body upon receiving the request, add the note item to the database, and return the newly-created record back to the React app.
If the mutation resolves successfully, React will re-render the DOM to display the note item in the UI. Otherwise, an alert notification will be displayed to list the errors returned by the API.
Perform the UPDATE Functionality
To edit a note item in the database, click on the three dots (…) next to the date element and select the Edit option to display the Update Note modal. On the Update Note popup, edit the title or content and click the Update Note button to submit the form.
If the form is valid, the useUpdateNoteMutation()
hook will be evoked to submit modified data to the backend API. The backend API will then query the database to find the record that matches the provided ID and update its fields based on the data provided in the request body.

Once the mutation is successful, React will re-render the DOM to reflect the changes.
Perform the READ Functionality
When you visit the root route of the app, RTK Query will make a GET request to retrieve a paginated list of note items from the API. On successful query, React will re-render the DOM to display the list of note items in the UI.

Perform the DELETE Functionality
To delete a note item in the database, click on the three dots again and choose the Delete option. Because DELETE operations are expensive, you’ll be prompted to confirm your action before the useDeleteNoteMutation()
hook will be called.
When you click on the ok button, RTK Query will fire a DELETE request to the backend API. The API will then find the record and delete it from the database.
On successful mutation, the note item will be removed from the DOM which will reflect in the UI.

Conclusion
Congrats on reaching the end. In this article, you learned how to create a React.js CRUD app with Redux Toolkit and RTK Query hooks.
You can find the complete source code of the React Redux Toolkit project on GitHub
Hello, I’m creating my own project using all the packages you use.
I have the siteAPI.ts module and there is a logic to handle create a new site by post query inside.
It looks something like this:
const BASEURL = ‘/api/users/sites’;
export const siteAPI = createApi({
reducerPath: ‘siteAPI’,
baseQuery: fetchBaseQuery({ baseUrl: BASEURL }),
tagTypes: [‘Sites’],
endpoints: (builder) => ({
createSite: builder.mutation({
query(site) {
return {
url: ”,
method: ‘post’,
withCredentials: true,
body: site,
headers: {
‘X-CSRFToken’: cookies.get(‘csrftoken’),
},
};
},
transformResponse: (result: { site: Site }) => result.site,
invalidatesTags: [{ type: ‘Sites’, id: ‘LIST’ }],
})
})
It works, but how can I get a response with saved data?
My API returns to the client side the info:
{
“slug”: “string”,
“secret_key”: “string”,
“account”: {
“owner”: 0
},
“domain_name”: “string”,
“is_installed”: false
}
Once I get this response object, I need to set the slug as a URL query param.
How can I do that?
You can do that in the
transformResponse
method. Here is an example `export const siteAPI = createApi({
reducerPath: ‘siteAPI’,
baseQuery: fetchBaseQuery({ baseUrl: BASEURL }),
tagTypes: [‘Sites’],
endpoints: (builder) => ({
createSite: builder.mutation({
query(site) {
return {
url: ”,
method: ‘post’,
withCredentials: true,
body: site,
headers: {
‘X-CSRFToken’: cookies.get(‘csrftoken’),
},
};
},
transformResponse: (result: { site: Site }) => {
// Extract the slug value from the response
const { slug } = result.site;
// Set the slug as a URL query parameter (replace ‘window.location.search’ with your preferred method)
const queryParams = new URLSearchParams(window.location.search);
queryParams.set(‘slug’, slug);
// Update the URL with the new query parameters
window.history.replaceState({}, ”, `${window.location.pathname}?${queryParams.toString()}`);
// Return the transformed response
return result.site;
},
invalidatesTags: [{ type: ‘Sites’, id: ‘LIST’ }],
}),
}),
});
`