Refine: Built Admin Panels 10x Faster

Tired of building CRUD admin panels from scratch? Refine gives you authentication, routing, data tables out of the box. Built a complete dashboard in one day. Here's my guide.

Why I switched to Refine

Built admin panels the hard way for years. Create React App, install routing library, build authentication from scratch, create CRUD pages one by one, handle forms manually, manage state with Redux. Weeks of work for a simple dashboard.

Tried Ant Design Pro - powerful but opinionated. Wanted to use Material UI? Tough luck. Custom layouts? Fight the framework.

Then found Refine. Headless React framework for building admin panels. Gives you CRUD, routing, authentication, but stays out of UI decisions. Use any UI library. Build layouts your way. Focus on business logic instead of boilerplate.

Built my last admin panel in a day. Not a joke. Start to finish, with authentication, data tables, forms, filtering, pagination. Would've taken two weeks before.

What Refine actually does

React framework for admin panels and internal tools. Auto-generates CRUD pages from your data. Handles routing, authentication, permissions, data fetching. Headless design means you choose the UI library (Ant Design, Material UI, Tailwind CSS, whatever). Connects to any REST or GraphQL API.

Installation and setup

Quickest way to start with Refine.

Option 1: Create new app (recommended)

# Create Refine app with interactive CLI
npm create refine-app@latest

# Follow the prompts:
# - Project name: my-admin-panel
# - Select framework: React
# - Select UI library: Ant Design / Material UI / Mantine
# - Select backend: REST API / GraphQL / Supabase / NestJS
# - Select example: Basic / CRUD

Option 2: Install into existing project

# Install core package
npm install @refinedev/core

# Install UI library (example: Ant Design)
npm install antd @refinedev/antd

# Install router (if not present)
npm install react-router-dom

Basic setup

// src/App.tsx
import { Refine } from "@refinedev/core";
import { RefineThemes } from "@refinedev/antd";
import { ConfigProvider } from "antd";
import routerProvider from "@refinedev/react-router-v6";
import dataProvider from "@refinedev/simple-rest";

function App() {
  return (
    
      
        {/* Your routes */}
      
    
  );
}

Core concepts

Understand these and you understand Refine.

1. Data Providers

Abstraction layer between Refine and your backend. One interface, any backend.

// REST API
import dataProvider from "@refinedev/simple-rest";
const dataProvider = simpleRestDataProvider("https://api.example.com");

// GraphQL
import dataProvider from "@refinedev/graphql";
const dataProvider = graphqlDataProvider("https://api.example.com/graphql");

// Supabase
import dataProvider from "@refinedev/supabase";
const dataProvider = supabaseDataProvider(client);

// Use in Refine

Switch backends without changing your UI. Just swap data provider.

2. Resources

Define your data model and routes. Refine generates CRUD pages automatically.

const resources = [
  {
    name: "users",
    list: UserList,
    create: UserCreate,
    edit: UserEdit,
    show: UserShow,
    meta: {
      canDelete: true,
      label: "Users",
    },
  },
  {
    name: "products",
    list: ProductList,
    create: ProductCreate,
    edit: ProductEdit,
  },
];

Each resource has list, create, edit, show pages. Omit what you don't need.

3. Hooks for data fetching

Built-in hooks for CRUD operations. No need to write fetch logic.

import { useTable, useForm, useShow } from "@refinedev/core";

// List page - fetch data with pagination, sorting, filtering
const { tableProps } = useTable({
  resource: "users",
  sorters: {
    initial: [
      { field: "id", order: "desc" },
    ],
  },
});

// Create/Edit form
const { onFinish, formLoading } = useForm({
  resource: "users",
  action: "create", // or "edit"
});

// Show page
const { queryResult } = useShow({
  resource: "users",
  id: userId,
});

4. Authentication

Built-in auth providers for common scenarios.

import { AuthProvider } from "@refinedev/core";

const authProvider: AuthProvider = {
  login: async ({ username, password }) => {
    const response = await fetch("https://api.example.com/login", {
      method: "POST",
      body: JSON.stringify({ username, password }),
    });
    const data = await response.json();
    localStorage.setItem("token", data.token);
    return Promise.resolve();
  },
  logout: async () => {
    localStorage.removeItem("token");
    return Promise.resolve();
  },
  checkAuth: async () => {
    const token = localStorage.getItem("token");
    return token ? Promise.resolve() : Promise.reject();
  },
  checkError: async (error) => {
    if (error.status === 401) {
      return Promise.reject();
    }
    return Promise.resolve();
  },
  getPermissions: async () => {
    return Promise.resolve();
  },
};

Building a CRUD app

Let's build a user management system from scratch.

Step 1: Define resource

// src/App.tsx

  
    } />
    } />
    } />
    } />
  

Step 2: List page

// src/pages/users/list.tsx
import { List, useTable } from "@refinedev/antd";
import { Table, Space } from "antd";

export const UserList: React.FC = () => {
  const { tableProps } = useTable({
    resource: "users",
    sorters: {
      initial: [{ field: "id", order: "desc" }],
    },
  });

  return (
    
      
        
        
        
         (
            
              View
              Edit
            
          )}
        />
      
); };

Step 3: Create page

// src/pages/users/create.tsx
import { Create, useForm } from "@refinedev/antd";
import { Form, Input, Select, Button } from "antd";

export const UserCreate: React.FC = () => {
  const { formProps, saveButtonProps } = useForm({
    resource: "users",
    action: "create",
  });

  return (
    
      
); };

Step 4: Edit page

// src/pages/users/edit.tsx
import { Edit, useForm } from "@refinedev/antd";
import { Form, Input, Select } from "antd";

export const UserEdit: React.FC = () => {
  const { queryResult } = useShow();
  const { data } = queryResult;
  const record = data?.data;

  const { formProps, saveButtonProps } = useForm({
    resource: "users",
    action: "edit",
    id: record?.id,
  });

  return (
    
      
{/* Same form as create, but pre-filled */}
); };

Advanced features

Beyond basic CRUD.

1. Filtering and search

import { useTable } from "@refinedev/antd";
import { FilterDropdown, Input, Select } from "@antd";

const { tableProps, searchFormProps } = useTable({
  resource: "users",
  onSearch: (params) => {
    return [
      {
        field: "name",
        operator: "contains",
        value: params.name,
      },
      {
        field: "role",
        operator: "eq",
        value: params.role,
      },
    ];
  },
});

 (
    
      
    
  )}
/>

2. Relationships and foreign keys

// User belongs to a company
 {
    // Fetch company data
    const { data } = useOne({
      resource: "companies",
      id: value,
    });
    return data?.data?.name;
  }}
/>

3. File uploads

import { Upload } from "antd";


   {
      try {
        const response = await uploadFile(file);
        onSuccess(response);
        form.setFieldValue("avatar", response.url);
      } catch (error) {
        onError(error);
      }
    }}
  >
    
  

4. Real-time updates

import { useLive } from "@refinedev/core";

const { data } = useLive({
  resource: "users",
  // WebSocket connection for real-time updates
  liveMode: "auto",
});

// Table updates automatically when data changes

Backend integrations

Refine connects to anything.

REST API

import dataProvider from "@refinedev/simple-rest";

const dataProvider = simpleRestDataProvider("https://api.example.com");

GraphQL

import dataProvider from "@refinedev/graphql";

const dataProvider = graphqlDataProvider("https://api.example.com/graphql");

Supabase

import { dataProvider } from "@refinedev/supabase";
import { createClient } from "@supabase/supabase-js";

const supabaseClient = createClient("URL", "KEY");
const dataProvider = supabaseDataProvider(supabaseClient);

NestJS CRUD

import dataProvider from "@refinedev/nestjs-crud";

const dataProvider = nestjsCrudDataProvider("https://api.example.com");

Airtable

import dataProvider from "@refinedev/airtable";

const dataProvider = airtableDataProvider("API_KEY", "BASE_ID");

UI library choices

Refine is headless. Choose any UI library.

Ant Design (most popular)

npm install @refinedev/antd antd

Enterprise-grade components. Great for internal tools. Large component library.

Material UI

npm install @refinedev/mui @mui/material

Google's design system. Clean, modern look. Good documentation.

Mantine

npm install @refinedev/mantine @mantine/core

Modern, customizable. Great hooks library. Active community.

Headless (build your own)

npm install @refinedev/core

Complete control. Build UI with Tailwind CSS, Chakra UI, whatever.

Refine vs alternatives

Quick comparison with other admin frameworks.

Feature Refine Ant Design Pro React Admin
Learning curve Moderate Moderate Moderate
UI flexibility High (headless) Low (Ant Design only) Moderate (Material UI)
Backend support REST, GraphQL, Supabase, Airtable REST REST, GraphQL
TypeScript support Excellent Good Good
CRUD generation Yes Partial Yes
Community Growing Large Large

Common issues and fixes

Stuff that breaks and how to fix it.

Issue: Data not loading in table

Fix: Check data provider URL. Verify API returns correct format. Check browser network tab. Ensure resource name matches API endpoint.

Issue: Create/Edit forms not saving

Fix: Verify form field names match API expected format. Check onFinish is being called. Ensure data provider supports create/update operations.

Issue: Authentication not working

Fix: Verify auth provider methods return correct values. Check checkAuth logic. Ensure login stores token correctly. Verify token format with backend.

Issue: Filters not applied

Fix: Check filter operator values. Verify backend supports filtering. Use correct filter format: { field: "name", operator: "contains", value: "John" }

Issue: TypeScript errors

Fix: Ensure types are imported correctly. Check generic types in hooks: useTable<IUser>(). Update to latest Refine version.

Best practices from production use

Lessons learned from real projects.

1. Start with CLI

Use npm create refine-app@latest for first project. Faster than manual setup. Good way to learn structure.

2. Define resources early

Map out all resources (users, products, orders) before coding. Less refactoring later. Use consistent naming conventions.

3. Create reusable components

Extract common table columns, form fields into reusable components. Reduces boilerplate across resources.

4. Use TypeScript

Refine has excellent TypeScript support. Define interfaces for your data models. Catches errors at compile time.

5. Implement error handling

Use useOnError to handle API errors globally. Show user-friendly notifications.

6. Add loading states

Use formLoading and tableProps.loading to show spinners during data fetching. Better UX.

7. Implement permissions

Use canAccess prop on resources to control access based on user roles. Protect sensitive operations.

Bottom line

Refine isn't magic. It's just a really well-designed abstraction layer for admin panels. But that abstraction saves weeks of development time.

CRUD pages that took days now take hours. Authentication is configured in minutes. Switching backends is a single line change. UI flexibility means my admin panels don't look like generic templates.

Best part: it stays out of your way. Not opinionated about UI. Choose your components, build your layouts. Refine handles the boring stuff - data fetching, routing, state management.

If you're building admin panels, internal tools, or dashboards, give Refine a shot. Start with their example project. Build a simple CRUD app. See how fast it goes.

Been using it for 6 months now. Built 5 admin panels. Each one faster than the last. Can't imagine going back to building from scratch.