On this page
React app with Vite
React is the most widely used JavaScript frontendlibrary.
In this tutorial we'll build a simple React app with Deno. The app will displaya list of dinosaurs. When you click on one, it'll take you to a dinosaur pagewith more details. You can see thefinished app repo on GitHuband ademo of the app on Deno Deploy
Want to skip the tutorial and deploy the finished app right now? Click thebutton below to instantly deploy your own copy of the complete SvelteKitdinosaur app to Deno Deploy. You'll get a live, working application that you cancustomize and modify as you learn!
Create a basic react app with ViteJump to heading
This tutorial will useVite to serve the app locally.Vite is a build tool and development server for modern web projects. It pairswell with React and Deno, leveraging ES modules and allowing you to import Reactcomponents directly.
In your terminal run the following command to create a new React app with Viteusing the typescript template:
$ deno init--npm vite my-react-app--template react-tsRun the dev serverJump to heading
Change directory to your new react app and install the dependencies:
cd<your_new_react_app>denoinstallNow you can serve your new react app by running:
deno run devThis will start the Vite server, click the output link to localhost to see yourapp in the browser.
Configure the projectJump to heading
We're going to build a full-stack React app with a Deno backend. We'll need toconfigure both vite and Deno to work together.
Install the deno plugin for Vite, the React types and the Vite React plugin:
denoadd npm:@deno/vite-plugin@latest npm:@types/react@latest npm:@vitejs/plugin-react@latestWe'll also need to install the Oak web framework for Deno to handle our APIrequests, and CORS middleware to allow cross-origin requests from the React app:
denoadd jsr:@oak/oak jsr:@tajpouria/corsThis will add these dependencies to a newdeno.json file.
In that file, we'll also add some tasks to make it easier to run the app indevelopment and production modes and some configuration to set up Deno withReact and Vite. Add the following to yourdeno.json file:
"tasks":{"dev":"deno run -A npm:vite & deno run server:start","build":"deno run -A npm:vite build","server:start":"deno run -A --watch ./api/main.ts","serve":"deno run build && deno run server:start"},"nodeModulesDir":"auto","compilerOptions":{"types":["react","react-dom","@types/react"],"lib":["dom","dom.iterable","deno.ns"],"jsx":"react-jsx","jsxImportSource":"react"}We can use bothpackage.json anddeno.json for dependency and configuration,but if you'd rather you can remove thepackage.json file and use onlydeno.json for your project configuration, be sure to move across thedependencies frompackage.json todeno.json imports first.
Add a backend APIJump to heading
Our project will have a backend API that serves dinosaur data. This API will bebuilt using Deno and Oak, and will provide endpoints to fetch a list ofdinosaurs and details about a specific dinosaur from a JSON file. In aproduction app this data would likely come from a database, but for thistutorial we'll use a static JSON file.
In the root of your project, create a new directory calledapi. In thisdirectory, create a file calleddata.json and copy acrossthe dinosaur data.
Next make a file calledmain.ts in theapi directory. This file will containthe Oak server code to handle API requests. In this file we will set up the Oakserver, define API routes, and serve static files for the React app. First setup the imports and create the Oak application and router:
import{ Application, Router}from"@oak/oak";import{ oakCors}from"@tajpouria/cors";import routeStaticFilesFromfrom"./util/routeStaticFilesFrom.ts";import datafrom"./data.json"with{ type:"json"};exportconst app=newApplication();const router=newRouter();Then we'll define the two main API routes:
router.get("/api/dinosaurs",(context)=>{ context.response.body= data;});router.get("/api/dinosaurs/:dinosaur",(context)=>{if(!context?.params?.dinosaur){ context.response.body="No dinosaur name provided.";}const dinosaur= data.find((item)=> item.name.toLowerCase()=== context.params.dinosaur.toLowerCase()); context.response.body= dinosaur??"No dinosaur found.";});Finally, we'll configure the server with middleware and start it listening:
app.use(oakCors());app.use(router.routes());app.use(router.allowedMethods());app.use(routeStaticFilesFrom([`${Deno.cwd()}/dist`,`${Deno.cwd()}/public`,]));if(import.meta.main){console.log("Server listening on port http://localhost:8000");await app.listen({ port:8000});}The server handles CORS, serves the API routes, and also serves static filesfrom thedist (built app) andpublic directories.
Serve static filesJump to heading
The Oak server will also serve the built React app. To do this, we need toconfigure it to serve static files from thedist directory where Vite outputsthe built app. We can use therouteStaticFilesFrom utility function to setthis up. Create a new file calledutil/routeStaticFilesFrom.ts in theapidirectory with the following code:
import{ Context, Next}from"jsr:@oak/oak";exportdefaultfunctionrouteStaticFilesFrom(staticPaths:string[]){returnasync(context: Context<Record<string, object>>, next: Next)=>{for(const pathof staticPaths){try{await context.send({ root: path, index:"index.html"});return;}catch{continue;}}awaitnext();};}This utility function attempts to serve static files from the provided paths,falling back to the next middleware if no file is found. It will serve theindex.html file from thedist directory, which is the entry point for theReact app.
You can test the API by runningdeno run dev and visitinglocalhost:8000/api/dinosaurs in your browser to see the JSON response with alldinosaurs.
React app setupJump to heading
Entry pointJump to heading
The React app entry point is insrc/main.tsx. We don't need to change anythinghere, but it's worth noting that this is where the React app is rendered intothe DOM. ThecreateRoot function fromreact-dom/client is used to render theApp component into theroot element inindex.html. Here's the code insrc/main.tsx:
import{ StrictMode}from"react";import{ createRoot}from"react-dom/client";import"./index.css";import Appfrom"./App.tsx";createRoot(document.getElementById("root")!).render(<StrictMode><App/></StrictMode>,);Add a routerJump to heading
The app will have two routes:/ and/:dinosaur.
We'll set up the routing insrc/App.tsx:
import{ BrowserRouter, Route, Routes}from"react-router-dom";import Indexfrom"./pages/index.tsx";import Dinosaurfrom"./pages/Dinosaur.tsx";functionApp(){return(<BrowserRouter><Routes><Routepath="/"element={<Index/>}/><Routepath="/:selectedDinosaur"element={<Dinosaur/>}/></Routes></BrowserRouter>);}exportdefault App;Proxy to forward the api requestsJump to heading
Vite serves the React application on port3000 while the API runs on port8000. We'll need to set up proxy configuration invite.config.ts to forwardAPI requests:
import{ defineConfig}from"vite";import reactfrom"@vitejs/plugin-react";import denofrom"@deno/vite-plugin";exportdefaultdefineConfig({ server:{ port:3000, proxy:{"/api":{ target:"http://localhost:8000", changeOrigin:true,},},}, plugins:[react(),deno()], optimizeDeps:{ include:["react/jsx-runtime"],},});Create the pagesJump to heading
Create a new directory calledpages, and inside we'll make two new filessrc/pages/index.tsx andsrc/pages/Dinosaur.tsx. TheIndex page lists alldinosaurs and theDinosaur page shows details of a specific dinosaur.
index.tsxJump to heading
This page fetches the list of dinosaurs from the API and renders them as links:
import{ useEffect, useState}from"react";import{ Link}from"react-router-dom";exportdefaultfunctionIndex(){const[dinosaurs, setDinosaurs]=useState([]);useEffect(()=>{(async()=>{const response=awaitfetch(`/api/dinosaurs/`);const allDinosaurs=await response.json();setDinosaurs(allDinosaurs);})();},[]);return(<mainid="content"><h1>🦕 Dinosaur app</h1><p>Click on a dinosaur below to learn more.</p>{dinosaurs.map((dinosaur:{ name:string; description:string})=>{return(<Linkto={`/${dinosaur.name.toLowerCase()}`}key={dinosaur.name}className="dinosaur">{dinosaur.name}</Link>);})}</main>);}Dinosaur.tsxJump to heading
This page will fetch the details of a specific dinosaur from the API and renderit in a paragraph:
import{ useEffect, useState}from"react";import{ Link, useParams}from"react-router-dom";exportdefaultfunctionDinosaur(){const{ selectedDinosaur}=useParams();const[dinosaur, setDino]=useState({ name:"", description:""});useEffect(()=>{(async()=>{const resp=awaitfetch(`/api/dinosaurs/${selectedDinosaur}`);const dino=await resp.json();setDino(dino);})();},[selectedDinosaur]);return(<div><h1>{dinosaur.name}</h1><p>{dinosaur.description}</p><Linkto="/">🠠 Back to all dinosaurs</Link></div>);}Styling your appJump to heading
We've writtensome basic styles for you,which can be copied intosrc/index.css.
Run the appJump to heading
To run the app, use the dev task defined indeno.json:
deno run devThis command will:
- Start the Vite development server on port 3000
- Start the API server on port 8000
- Set up the proxy to forward
/apirequests from the frontend to the backend
Navigate tolocalhost:3000 in your browser and you should see the dinosaur appwith a list of dinosaurs that you can click through to learn about each one.
Understanding the project structureJump to heading
Let's walk through the key files and folders in this project:
tutorial-with-react/├── api/ # Backend API│ ├── data.json # Dinosaur data (700+ dinosaurs)│ ├── main.ts # Oak server with API routes│ └── util/│ └── routeStaticFilesFrom.ts├── src/ # React frontend│ ├── main.tsx # React app entry point│ ├── App.tsx # Main app with routing│ ├── index.css # Global styles│ └── pages/│ ├── index.tsx # Homepage with dinosaur list│ └── Dinosaur.tsx # Individual dinosaur page├── public/ # Static assets├── deno.json # Deno configuration and tasks├── package.json # npm dependencies for Vite├── vite.config.ts # Vite configuration with proxy└── index.html # HTML templateKey conceptsJump to heading
Hybrid dependency management: The project uses both Deno and npmdependencies. Deno handles server-side dependencies like Oak, while npmhandles frontend dependencies through Vite.
Development vs Production: In development, Vite serves the React app onport 3000 and proxies API requests to the Oak server on port 8000. Inproduction, the Oak server serves both the built React app and the API fromport 8000.
Modern React patterns: The app uses React 19, functional components,hooks, and React Router for navigation.
Type safety: While this example doesn't use a separate types file, in alarger app you'd typically create TypeScript interfaces for your datastructures.
You can see a version of theapp running on Deno Deploy
Build and deployJump to heading
We set up the project with aserve task that builds the React app and servesit with the Oak backend server. Run the following command to build and serve theapp in production mode:
deno run builddeno run serveThis will:
- Build the React app using Vite (output goes to
dist/) - Start the Oak server which serves both the API and the built React app
Visitlocalhost:8000 in your browser to see the production version of the app!
You can deploy this app to your favorite cloud provider. We recommend usingDeno Deploy for a simple and easy deploymentexperience. You can deploy your app directly from GitHub, simply create a GitHubrepository and push your code there, then connect it to Deno Deploy.
Create a GitHub repositoryJump to heading
Create a new GitHub repository, then initialize andpush your app to GitHub:
git init-b maingit remoteadd origin https://github.com/<your_github_username>/<your_repo_name>.gitgitadd.git commit-am'my react app'git push-u origin mainDeploy to Deno DeployJump to heading
Once your app is on GitHub, you candeploy it to Deno Deploy.
For a walkthrough of deploying your app, check out theDeno Deploy tutorial.
🦕 Now you can scaffold and develop a React app with Vite and Deno! You’re readyto build blazing-fast web applications. We hope you enjoy exploring thesecutting-edge tools, we can't wait to see what you make!