Build fast and customizable admin panel with NestJS.
Tags: nestjs, refine, crud, react
Published: 2021-10-04 14:33:55

---
canonical_url: https://refine.dev/blog/customizable-admin-panel-with-nestjs/
title: Build fast and customizable admin panel with NestJS.
published: true
description: 
tags: nestjs, refine, crud, react 
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69637h31xt9byx35xhqv.jpg
---

All the steps described are in this [repo](https://github.com/pankod/refine/tree/master/examples/blog/jobPosting).

## Intro
[NestJS](https://github.com/nestjs/nest) is a framework for building efficient, scalable Node.js server-side applications. With [nestjsx/crud](https://github.com/nestjsx/crud) we can add CRUD functions quickly and effortlessly on this framework.

In this article, we will prepare a simple `job-posting` application. We will also use the [refine](https://github.com/pankod/refine) framework for the **admin panel**. The project will consist of two parts, api and admin.

### NestJS Rest Api

To start playing with NestJS you should have node (>= 10.13.0, except for v13) and [npm](https://nodejs.org) installed.

**Create Project Folder**

```bash
mkdir job-posting-app
cd job-posting-app
```

Setting up a new project is quite simple with the [Nest CLI](https://docs.nestjs.com/cli/overview). With npm installed, you can create a new Nest project with the following commands in your OS terminal:

```bash
npm i -g @nestjs/cli
nest new api
```

[TypeORM](https://github.com/typeorm/typeorm) is definitely the most mature ORM available in the node.js world. Since it's written in TypeScript, it works pretty well with the Nest framework. I chose mysql as database. TypeORM supports many databases (MySQL, MariaDB, Postgres etc.)

To start with this library we have to install all required dependencies:

```bash
npm install --save @nestjs/typeorm @nestjs/config typeorm mysql2
```

- Create an [.env.example](https://github.com/pankod/refine-hackathon/tree/main/job-posting-app/blob/master/api/.env.example) file. Here we will save the database information.
- Create and configured a [docker-compose](https://github.com/pankod/refine-hackathon/tree/main/job-posting-app/blob/master/api/docker-compose.yml) file for MySQL.
- Create a [ormconfig.ts](https://github.com/pankod/refine-hackathon/tree/main/job-posting-app/blob/master/api/ormconfig.ts) file for migrations.
- Add the following scripts to the `package.json` file for migrations.
```bash
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
"db:migration:generate": "npm run typeorm -- migration:generate",
"db:migration:run": "npm run typeorm -- migration:run",
"db:migration:revert": "npm run typeorm -- migration:revert",
"db:refresh": "npm run typeorm schema:drop && npm run db:migration:run"
```
- Import the `TypeOrmModule` into the `app.module.ts`

**Install nestjsx-crud**
I used [nestjsx-crud](https://github.com/nestjsx/crud) library because it makes crud functions easier.

```bash
npm i @nestjsx/crud @nestjsx/crud-typeorm class-transformer class-validator
```

*Since the steps to create Entities Contorllers, and services are very long, I do not explain step by step. You can check the [repo](https://github.com/pankod/refine-hackathon/tree/main/job-posting-app) for details.*

It created these end-points automatically with nestjsx/crud.

![Swagger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/opkvo6yvdil0tppfb4fa.png)

**Now let's refine the admin panel.** With [Superplate](https://pankod.github.io/superplate/docs), we can quickly create a `refine` project.

```bash
npx superplate-cli admin
```

Answer as below:

```
✔ Select your project type › refine
✔ What will be the name of your app · admin
✔ Do you want to customize theme?: · less
✔ Data Provider: · nestjsx-crud-data-provider
✔ Auth Provider: · none
✔ Do you want to add an example page? · example-resource
✔ Do you want to customize layout? · custom-layout
✔ i18n - Internationalization: · no
```

```bash
cd admin
npm run dev
```

Refine's sample application will welcome you.

![Refine](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oz45z207pxe6x4bryvgw.png)

Change api url in [admin/src/App.tsx](https://github.com/pankod/refine-hackathon/tree/main/job-posting-app/blob/master/admin/src/App.tsx)

```
const API_URL = "http://localhost:3000";
```

Let's add the listing page in refine for the `companies` crud end-point.

*/admin/src/pages/companies/list.tsx*
```tsx
import {
  List,
  Table,
  TextField,
  useTable,
  IResourceComponentsProps,
  getDefaultSortOrder,
  Space,
  EditButton,
  DeleteButton,
  TagField,
  ShowButton,
} from "@pankod/refine";
import { ICompany } from "interfaces";

export const CompanyList: React.FC<IResourceComponentsProps> = () => {
  const { tableProps, sorter } = useTable<ICompany>({
    initialSorter: [
      {
        field: "id",
        order: "desc",
      },
    ],
  });

  return (
    <List>
      <Table {...tableProps} rowKey="id">
        <Table.Column
          dataIndex="id"
          key="id"
          title="ID"
          render={(value) => <TextField value={value} />}
          defaultSortOrder={getDefaultSortOrder("id", sorter)}
          sorter
        />
        <Table.Column
          dataIndex="name"
          key="name"
          title="Name"
          render={(value) => <TextField value={value} />}
          defaultSortOrder={getDefaultSortOrder("name", sorter)}
          sorter
        />
        <Table.Column
          dataIndex="location"
          key="location"
          title="Location"
          render={(value) => <TextField value={value} />}
          defaultSortOrder={getDefaultSortOrder("location", sorter)}
          sorter
        />
        <Table.Column
          dataIndex="isActive"
          key="isActive"
          title="Is Active"
          render={(value) => <TagField value={value} />}
          defaultSortOrder={getDefaultSortOrder("status", sorter)}
          sorter
        />
        <Table.Column<ICompany>
          title="Actions"
          dataIndex="actions"
          render={(_, record) => (
            <Space>
              <EditButton hideText size="small" recordItemId={record.id} />
              <ShowButton hideText size="small" recordItemId={record.id} />
              <DeleteButton hideText size="small" recordItemId={record.id} />
            </Space>
          )}
        />
      </Table>
    </List>
  );
};
```

Similarly, let's add the create, edit and jobs crud pages under the pages folder.

Next, let's define the resources in `<Refine>` (App.tsx):

```tsx
      <Refine
           dataProvider={dataProvider}
           ...
            resources={[
                {
                   name: "companies",
                   list: CompanyList,
                   create: CompanyCreate,
                   edit: CompanyEdit,
                   show: CompanyShow,
                },
                {
                   name: "jobs",
                   list: JobList,
                   create: JobCreate,
                   edit: JobEdit,
                   show: CompanyShow,
                },
           ]}
    />
```

![Refine](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tyitbm11gl4v8d8zrcq.png)