Building a React Data Grid CRUD Admin App 30 Minutes
요약; DR 관리자용 CRUD 앱을 만들어본 적이 있다면 루틴을 아실 겁니다: 거의 맞는 그리드, 미완성 모달 폼, "행 편집은 나중에 하겠습니다" 포스트잇, 그리고 3주간의 "날짜 열에 필터가 작동하지 않음" 티켓. 저는 클래식 Northwind 관리 콘솔을 30분도 채 안 되어 재구축했습니다 — 전면에 걸쳐 완전한 CRUD [...]
TL;DR
관리자용 CRUD 앱을 만들어본 적이 있다면 루틴을 알 것입니다: 거의 맞는 그리드, 미완성 모달 폼, "행 편집은 나중에 하겠습니다"라는 포스트잇, 그리고 3주간의 "날짜 열에 필터가 작동하지 않는다" 티켓.
저는 30분도 채 안 되어 고전적인 Northwind 관리자 콘솔을 재구축했습니다 — 주문, 고객, 제품, 배송자, 영업사원 전반에 걸쳐 완전한 CRUD를 구축했습니다 — UI는 Ignite UI React Data Grid를, 백엔드에는 작은.NET 10 + EF Core SQLite API를 사용했습니다. 전체 UI 코드: 약 1,500줄의 TypeScript. 그리드와 싸운 총 시간: ~0분.
실제 관리자 앱용 React 데이터 그리 드를 평가한다면, 이 사용 사례가 중요합니다: 인라인 편집, 필터링, 관계형 드롭다운, 검증, 마스터 디테일, 요약, 그리고 페이지가 장난감이 아니면 모두 잘 유지됩니다.
Repo: react-grids/react-data-grids-crud

그게 바로 제가 가져가길 원하는 스크린샷입니다. 어떤 행→ 올리면 액션 스트립이 편집과 삭제 버튼으로 미끄러져 들어갑니다. 편집을 클릭→하면 해당 행이 인라인 편집 가능해지고, 저장/취소 칩이 나타납니다. 드롭→다운을 클릭해 타이핑 중 검색할 수 있는 조합을 클릭하세요. 한 →행을 선택하면 그 아래에 연결된 데이터가 있는 마스터 디테일 패널이 열립니다.
그건 커스텀 빌드가 아니에요. 그건 플러스<IgrGrid rowEditable>야<IgrActionStrip><IgrGridEditingActions /></IgrActionStrip>. 코드는 곧 다룰게.
CRUD 데모는 쉽습니다. CRUD 앱은 그렇지 않습니다.
내부 관리자 앱이 어디에나 있습니다. 모든 회사에 그런 게 있어요. 데모 릴에는 절대 들어가지 않아요. 그럼에도 불구하고, 한 줄 한 줄씩 말해보면 이들은 만들 수 있는 가장 어려운 UI 중 하나인데, 사용자가 실제로 매일 사용 하며 거친 부분을 모두 알아차리기 때문입니다.
진짜 CRUD 페이지에는 다음이 필요합니다:
- 정렬, 필터링, 페이징(간단해 보이지만 날짜 유형과 지역을 넘어 시도해보세요)
- 적절한 저장/취소 의미와 낙관적인 사용자 경험(UX)을 적용한 인라인 행 편집
- 풍부한 관계 조회를 가진 생성 대상 대화 형식
- 사용자가 저장을 허용하기 전에 실행되는 검증
- 사용자가 클릭하지 않고도 맥락을 볼 수 있도록 마스터 디테일 입니다
- A delete confirmation that doesn’t feel hostile
- 명확하게 전달되는 로딩 / 빈 상태 / 오류 상태
- 키보드 접근성, 초점 관리, 스크린 리더 라벨
대부분의 팀은 그 중 60%를 출포하고 승리를 선언한 뒤 어깨를 으쓱하며 작전팀에게 넘깁니다. 그것이 Ignite UI React 데이터 그리드가 사라지게 하려는 작업입니다.
.NET 코어, SQLite로 CRUD 백엔드를 설정하는 과정.
백엔드는 순수한 보일러플레이트입니다. 다섯 개의 엔터티, 하나의 EF Core DbContext, 다섯 개의 작은 CRUD 컨트롤러, Swashbuckle for OpenAPI:
// server/Program.cs
builder.Services.AddDbContext<AppDbContext>(opts =>
opts.UseSqlite("Data Source=northwind.db"));
builder.Services.AddControllers();
builder.Services.AddSwaggerGen();
컨트롤러는 예상하신 그대로입니다:
// server/Controllers/ProductsController.cs
[ApiController, Route("api/[controller]")]
public class ProductsController(AppDbContext db) : ControllerBase
{
[HttpGet] public Task<List<Product>> GetAll() => db.Products.AsNoTracking().ToListAsync();
[HttpPost] public async Task<Product> Create(Product p) { db.Add(p); await db.SaveChangesAsync(); return p; }
[HttpPut("{id:int}")]public async Task Update(int id, Product p) { db.Update(p); await db.SaveChangesAsync(); }
[HttpDelete("{id:int}")] public async Task Delete(int id) { db.Remove(await db.Products.FindAsync(id)); await db.SaveChangesAsync(); }
}
dotnet run그리고 Swagger UI/swagger가 나옵니다. 이제 프론트엔드를 작성하지 않고도 모든 엔드포인트를 보고 호출할 수 있습니다. 이 계약은 React 쪽을 쉽게 만들어줍니다: 프론트엔드가 줄 단위로 미러링할 수 있는 타입 기반 REST API입니다.
프론트엔드 서비스 계층은 API를 미러링합니다
// client/src/api/types.ts
export interface Product {
productID: number;
productName: string;
unitPrice: number;
stockLevel: number;
}
// client/src/api/services.ts
export const ProductsService = {
list: () => api.get<Product[]>('/api/Products'),
get: (id: number) => api.get<Product>(`/api/Products/${id}`),
create: (p: Omit<Product, 'productID'>) => api.post<Product>('/api/Products', p),
update: (p: Product) => api.put<void>(`/api/Products/${p.productID}`, p),
remove: (id: number) => api.del(`/api/Products/${id}`),
};
TypeScript 타입은 기본적으로 직렬화 ASP.NET JSON 형태와 일치하기 때문에 매핑 계층을 유지할 필요가 없습니다.
이 글 전체가 다루는 부분: React 데이터 그리드
여기 제품 페이지의 전체 CRUD 배선이 있습니다.
import {
IgrGrid, IgrColumn, IgrPaginator,
IgrActionStrip, IgrGridEditingActions,
} from 'igniteui-react-grids';
<IgrGrid
data={filtered}
primaryKey="productID"
rowEditable={true}
allowFiltering={true}
filterMode="excelStyleFilter"
moving={true}
onRowEditDone={async (e: CustomEvent) => {
const row = (e.detail as { newValue?: Product })?.newValue;
if (!row) return;
try {
await ProductsService.update(row);
toast.success(`Product "${row.productName}" saved.`);
products.refetch();
} catch (err) {
toast.error(`Save failed: ${(err as Error).message}`);
}
}}
onRowDeleted={async (e: CustomEvent) => {
const row = (e.detail as { data?: Product })?.data;
if (!row) return;
try {
await ProductsService.remove(row.productID);
toast.success(`Deleted "${row.productName}".`);
products.refetch();
} catch (err) {
toast.error(`Delete failed: ${(err as Error).message}`);
}
}}
>
<IgrColumn field="productID" header="ID" editable={false} />
<IgrColumn field="productName" header="Product" editable={true} hasSummary />
<IgrColumn field="unitPrice" header="Price"
dataType="number" editable={true}
bodyTemplate={(c) => <span>{currency(c.cell.value)}</span>} />
<IgrColumn field="stockLevel" header="Stock"
dataType="number" editable={true} hasSummary />
<IgrPaginator perPage={10} />
<IgrActionStrip>
<IgrGridEditingActions addRow={false} />
</IgrActionStrip>
</IgrGrid>
이렇게 하면 한 가지 요소가 다음과 같습니다:
- Sortable columns (click the header)
- 엑셀 스타일의 열 필터(각 헤더의 메뉴)
- 페이지 작성 (페이지당 15/30/45) 그리고 푸터 페이지네이터
- 드래그 후 순서 변경(드래그 → 재정렬)
- 푸터에 수치 및 문자열 요약(평균, 합, 개수)
- 작업 스트립→ 편집 /삭제 아이콘이 있는 행 마우스 마우스
- 편집을 클릭하면 셀→ 편집 가능 해지고, 저장/취소 칩이 행 하단에 나타납니다
onRowEditDone병합된 새 행과 함께 파이어→ API로 PUTonRowDeleted사용자가 API에서 삭제→ DELETE it 를 확인하면 실행됩니다
제가 언급하고 싶은 부분은 플러스rowEditable={true} 콤보입니다IgrActionStrip + IgrGridEditingActions. 직접 추적하거나 편집하는 게 아니에요. 플래그도, '더티 셀' 맵도, '사용자가 Escape를 눌렀는가' 핸들러도 없isEditing 었습니다. React 데이터 그리드가 이를 관리하며; 커밋을 듣고 서비스로 푸시하면 됩니다.
다른 하나는 원하는 대로 렌더링 bodyTemplate. 포맷 맞추고 있어요unitPrice 위 예시에서 화폐로 사용하죠. 같은 훅 덕분에 상태 알약, 아바타, 배지, 색상으로 구분된 스톡 레벨 등 React 렌더링할 수 있는 모든 것을 렌더링할 수 있습니다.
관계형 조회는 숫자 입력이 아니라 Combo에 속합니다
인라인 편집은 프리미티브에 좋지만, 행에 외래 키(salesPersonID,,productIDshipperID)가 있으면 사용자가 ID를 입력하지 않게 됩니다. 검색 가능한 그룹화된 드롭다운을 원합니다.
Enter IgrCombo:
<IgrCombo
data={salespeople.map((s) => ({
id: s.salesPersonID,
name: fullName(s.salesPersonFirstName, s.salesPersonLastName),
subtitle: s.salesPersonTitle ?? '',
}))}
valueKey="id"
displayKey="name"
groupKey="subtitle" // ← grouped headers come for free
singleSelect
value={form.salesPersonID != null ? [form.salesPersonID] : []}
onChange={(e) => {
const arr = (e.detail as { newValue?: unknown[] }).newValue ?? [];
setForm((f) => ({ ...f, salesPersonID: (arr[0] as number) ?? null }));
}}
/>

사용자들이 좋아할 Ignite UI 기업용 기능들 – 타자 앞쪽 필터링, 칩 렌더링을 통한 다중 선택, 수천 개의 항목에 대한 가상화, 그리고 맞춤 아이템 템플릿 슬롯 등.
와IgrComboIgrDatePicker 결합IgrInput 하면 '풍부한 형태 창조' 스토리가 Ignite UI React 컴포넌트가 처리합니다.
테마 — 한 번의 임포트, 완료
이 앱을 만들기 시작할 때는 단일 테마, 즉 내장된 Material Light 테마를 파일당 한 번의 CSS 가져오기로 적용했습니다:
// main.tsx import 'igniteui-webcomponents/themes/light/material.css'; import 'igniteui-react-grids/grids/themes/light/material.css';
모든 Ignite UI 표면 — 쉐브론, 액션 스트립, 편집 연필, 페이지네이터 버튼, 대화 헤더 — 가 자동으로 테마를 가져옵니다. 앱이 발전함에 따라, 저는 네 가지 디자인 시스템(Material, Fluent, Indigo, Bootstrap)을 기반으로 테마를 교체할 수 있는 선택기를 추가했고, 각 시스템마다 다크 버전도 적용했습니다. CSS 가져오기 하나만 바꾸고 앱 전체를 다시 테마로 바꾸세요.
I also used the Ignite UI MCP servers
이 작업은 Ignite UI MCP 서버와 테마 설정에 사용해 만들었습니다. 모든 페이지를 처음부터 직접 작업하는 대신, 프롬프트를 사용해 앱 구조(프론트엔드와 백엔드), 컴포넌트 사용, 시각적 설정을 약 20분 만에 생성하고 다듬었습니다.
어떻게 했는지 정확히 보고 싶다면, 프롬프트는 GitHub 저장소에 있습니다: docs/PROMPTS.md. 제가 사용한 MCP 구성은 Ignite UI CLI MCP와 테마 MCP 워크플로우를 결합한 것입니다.
내가 직접 만들지 않아도 되는 것들
비교를 구체적으로 만들기 위해 — 제가 굳이 쓰 지 않아도 된 부분은 다음과 같습니다:
- 가상화된 테이블 렌더러
- 행 편집 상태 관리
- 엑셀 스타일의 컬럼 필터 UI
- 멀티셀렉트 그룹화 콤보 박스와 타이핑 기능이 있습니다
- A date picker that renders a calendar
- 크로스 브라우저 스크롤 동기화
- Keyboard navigation across cells
- 편집 대화 대화 공간의 포커스 관리
- 적절한 의미 색상을 가진 상태 완화 렌더링
- Toast / snackbar plumbing
- 위 모든 작품에서 일관된 시각적 스타일링을 유지하고 있습니다
그게 가치 문제야. Ignite UI React 데이터 그리드는 두 번 하고 싶지 않은 작업과 정확히 같은 모양입니다.
Try it
전체 출처는 github.com/react-grids/react-data-grids-crud에 있습니다. 복제해서 .NET API를 실행하고, Vite 개발 서버를 실행하면 두 터미널에서 작동하는 CRUD 콘솔이 완성됩니다.

React Data Grid를 자신의 앱에서 사용하고 싶다면, Ignite UI React Data Grid 페이지에서 시작해 거기서 무료 체험을 받아보세요.
유튜브에서 영상을 시청하세요:
여기서 사용한 그리드는 상업igniteui-react-grids 용 패키지입니다. 행 편집, 마스터 디테일, 액션 스트립이 필요 없다면 MIT 라이선스IgrGridLite에 무료로igniteui-react/grid-lite 제공될 수도 있습니다.
자주하는 질문
React에서 CRUD 관리자 앱을 가장 빠르게 만드는 방법은 무엇인가요?
가장 빠른 방법은 React 데이터 그리드를 시작하고, 이미 어려운 부분인 편집, 필터링, 페이징, 조회, 검증, 접근성을 처리하는 컴포넌트를 만드는 것입니다. 이 프로젝트에서는 Ignite UI React 데이터 그리드와 소규모 타입 서비스 계층이 CRUD 작업을 지연시키는 대부분의 맞춤형 배관을 제거했습니다.
React 데이터 그리드란 무엇이며, 언제 필요할까요?
React 데이터 그리드는 상호작용적이고 데이터 중심의 화면을 위해 설계된 고성능 테이블 컴포넌트입니다. 앱이 읽기 전용 행을 넘어 인라인 편집, 고급 필터링, 요약, 키보드 탐색, 가상화, 관계형 데이터 워크플로우를 요구할 때 필요합니다.
인라인 편집이 가능한 진짜 React CRUD 앱을 몇 분 만에 만들 수 있나요?
네, 사실 몇 분 만에 할 수 있어요. 이 데모에는 주문, 고객, 제품, 발송자, 영업사원을 포함하며, Ignite UI CLI와 Claude Code 또는 GitHub Copilot을 사용해 30분 이내에 인라인 행 편집, 대화 생성, 확인 삭제, 마스터-디테일, 관계형 드롭다운을 제공합니다.
왜 CRUD 앱에 Ignite UI for React을 사용하느냐, 그리드 동작을 직접 만드는 대신 말이죠?
CRUD 앱에서 비용이 많이 드는 부분은 행을 렌더링하는 것이 아니기 때문입니다. 문제는 그 행들에 대한 상호작용 모델입니다: 편집 수명 주기, 필터링 UX, 키보드 동작, 그룹 조회, 요약, 테마, 일관된 상태 전환 등. Ignite UI 이러한 동작을 제품 기능으로 제공하므로, 코드는 비즈니스 데이터와 API 호출에 집중합니다.
Ignite UI for React.NET 웹 API 백엔드와 함께 작동하나요?
네. 이 예시에서 프론트엔드는 표준 REST 엔드포인트를 통해 .NET 10 + EF Core SQLite API와 통신합니다. 클라이언트 코드는 TypeScript 서비스 함수 호출/api/Products과/api/Orders 다른 CRUD 경로로 구성됩니다.
Ignite UI MCP 서버로도 이걸 구축하셨나요?
네. 앱은 런타임에 Ignite UI 컴포넌트를 사용하며, 빌드 워크플로우는 Ignite UI CLI MCP와 테마 MCP 설정의 혜택도 누렸습니다. 이 조합은 AI 도구가 페이지를 스캐폴딩하고, 구성 요소 질문에 답하며, 테마 결정이 Ignite UI 패턴과 일치하도록 돕습니다.
Is Ignite UI React Data Grid free?
이 글에서 사용한 전체igniteui-react-grids 패키지는 상업용입니다. 더 가벼운 그리드만 필요하다면, Infragistics MIT 라이선스IgrGridLite 패키지에서 무료로igniteui-react/grid-lite 제공하지만, 행 편집과 액션 스트립 같은 고급 CRUD 기능도 상업용 패키지에 포함되어 있습니다.
프로덕션 CRUD 데이터 그리드에서 가장 중요한 기능은 무엇인가요?
기본은 정렬, 필터링, 페이징, 편집입니다. 실제로 팀은 관계형 조회, 검증, 삭제 확인, 키보드 접근성, 마스터-디테일 컨텍스트, 로딩 및 빈 상태, 그리고 페이지를 추가해도 무너지지 않는 테마 설정도 필요합니다. 이러한 특징들이 내부 공구가 신뢰할 수 있을지 깨지기 쉬운지 결정하는 요소들입니다.
Tags: #react #react-data-grid #typescript #crud #datagrid #ignite-ui #dotnet #admin-ui #mcp