On this page
Build a React app with a starter template
React is the most widely used JavaScript frontend library.
In this tutorial we'll build a simple React app with Deno. The app will display a list of dinosaurs. When you click on one, it'll take you to a dinosaur page with more details. You can see the finished app repo on GitHub and a demo of the app on Deno Deploy
Create a basic react app with Vite Jump to heading
This tutorial will use Vite to serve the app locally. Vite is a build tool and development server for modern web projects. It pairs well with React and Deno, leveraging ES modules and allowing you to import React components directly.
In your terminal run the following command to create a new React app with Vite using the typescript template:
deno run -A npm:create-vite@latest --template react-ts
Run the dev server Jump to heading
Change directory to your new react app and install the dependencies:
cd <your_new_react_app>
deno install
Now you can serve your new react app by running:
deno run dev
This will start the Vite server, click the output link to localhost to see your app in the browser.
Configure the project Jump to heading
We're going to build a full-stack React app with a Deno backend. We'll need to configure both vite and Deno to work together.
Install the deno plugin for Vite, the React types and the Vite React plugin:
deno add npm:deno-vite-plugin@latest npm:@types/react@latest npm:@vitejs/plugin-react@latest
We'll also need to install the Oak web framework for Deno to handle our API requests, and CORS middleware to allow cross-origin requests from the React app:
deno add jsr:@oak/oak@ jsr:@tajpouria/cors
This will add these dependencies to a new deno.json
file.
In that file, we'll also add some tasks to make it easier to run the app in
development and production modes and some configuration to set up Deno with
React and Vite. Add the following to your deno.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 both package.json
and deno.json
for dependency and configuration,
but if you'd rather you can remove the package.json
file and use only
deno.json
for your project configuration, be sure to move across the
dependencies from package.json
to deno.json
imports first.
Add a backend API Jump to heading
Our project will have a backend API that serves dinosaur data. This API will be built using Deno and Oak, and will provide endpoints to fetch a list of dinosaurs and details about a specific dinosaur from a JSON file. In a production app this data would likely come from a database, but for this tutorial we'll use a static JSON file.
In the root of your project, create a new directory called api
. In this
directory, create a file called data.json
and copy across
the dinosaur data.
Next make a file called main.ts
in the api
directory. This file will contain
the Oak server code to handle API requests. In this file we will set up the Oak
server, define API routes, and serve static files for the React app. First set
up the imports and create the Oak application and router:
import { Application, Router } from "@oak/oak";
import { oakCors } from "@tajpouria/cors";
import routeStaticFilesFrom from "./util/routeStaticFilesFrom.ts";
import data from "./data.json" with { type: "json" };
export const app = new Application();
const router = new Router();
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 files
from the dist
(built app) and public
directories.
Serve static files Jump to heading
The Oak server will also serve the built React app. To do this, we need to
configure it to serve static files from the dist
directory where Vite outputs
the built app. We can use the routeStaticFilesFrom
utility function to set
this up. Create a new file called util/routeStaticFilesFrom.ts
in the api
directory with the following code:
import { Context, Next } from "jsr:@oak/oak";
export default function routeStaticFilesFrom(staticPaths: string[]) {
return async (context: Context<Record<string, object>>, next: Next) => {
for (const path of staticPaths) {
try {
await context.send({ root: path, index: "index.html" });
return;
} catch {
continue;
}
}
await next();
};
}
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 the
index.html
file from the dist
directory, which is the entry point for the
React app.
You can test the API by running deno run dev
and visiting
localhost:8000/api/dinosaurs
in your browser to see the JSON response with all
dinosaurs.
React app setup Jump to heading
Entry point Jump to heading
The React app entry point is in src/main.tsx
. We don't need to change anything
here, but it's worth noting that this is where the React app is rendered into
the DOM. The createRoot
function from react-dom/client
is used to render the
App
component into the root
element in index.html
. Here's the code in
src/main.tsx
:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);
Add a router Jump to heading
The app will have two routes: /
and /:dinosaur
.
We'll set up the routing in src/App.tsx
:
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Index from "./pages/index.tsx";
import Dinosaur from "./pages/Dinosaur.tsx";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/:selectedDinosaur" element={<Dinosaur />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Proxy to forward the api requests Jump to heading
Vite serves the React application on port 3000
while the API runs on port
8000
. We'll need to set up proxy configuration in vite.config.ts
to forward
API requests:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import deno from "@deno/vite-plugin";
export default defineConfig({
server: {
port: 3000,
proxy: {
"/api": {
target: "http://localhost:8000",
changeOrigin: true,
},
},
},
plugins: [react(), deno()],
optimizeDeps: {
include: ["react/jsx-runtime"],
},
});
Create the pages Jump to heading
Create a new directory called pages
, and inside we'll make two new files
src/pages/index.tsx
and src/pages/Dinosaur.tsx
. The Index
page lists all
dinosaurs and the Dinosaur
page shows details of a specific dinosaur.
index.tsx Jump 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";
export default function Index() {
const [dinosaurs, setDinosaurs] = useState([]);
useEffect(() => {
(async () => {
const response = await fetch(`/api/dinosaurs/`);
const allDinosaurs = await response.json();
setDinosaurs(allDinosaurs);
})();
}, []);
return (
<main>
<h1>π¦ Dinosaur app</h1>
<p>Click on a dinosaur below to learn more.</p>
{dinosaurs.map((dinosaur: { name: string; description: string }) => {
return (
<Link
to={`/${dinosaur.name.toLowerCase()}`}
key={dinosaur.name}
className="dinosaur"
>
{dinosaur.name}
</Link>
);
})}
</main>
);
}
Dinosaur.tsx Jump to heading
This page will fetch the details of a specific dinosaur from the API and render it in a paragraph:
import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
export default function Dinosaur() {
const { selectedDinosaur } = useParams();
const [dinosaur, setDino] = useState({ name: "", description: "" });
useEffect(() => {
(async () => {
const resp = await fetch(`/api/dinosaurs/${selectedDinosaur}`);
const dino = await resp.json();
setDino(dino);
})();
}, [selectedDinosaur]);
return (
<div>
<h1>{dinosaur.name}</h1>
<p>{dinosaur.description}</p>
<Link to="/">π Back to all dinosaurs</Link>
</div>
);
}
Styling your app Jump to heading
We've written
some basic styles for you,
which can be copied into src/index.css
.
Run the app Jump to heading
To run the app, use the dev task defined in deno.json
:
deno run dev
This command will:
- Start the Vite development server on port 3000
- Start the API server on port 8000
- Set up the proxy to forward
/api
requests from the frontend to the backend
Navigate to localhost:3000
in your browser and you should see the dinosaur app
with a list of dinosaurs that you can click through to learn about each one.
Understanding the project structure Jump 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 template
Key concepts Jump to heading
-
Hybrid dependency management: The project uses both Deno and npm dependencies. Deno handles server-side dependencies like Oak, while npm handles frontend dependencies through Vite.
-
Development vs Production: In development, Vite serves the React app on port 3000 and proxies API requests to the Oak server on port 8000. In production, the Oak server serves both the built React app and the API from port 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 a larger app you'd typically create TypeScript interfaces for your data structures.
You can see a version of the app running on Deno Deploy
Build and deploy Jump to heading
The project includes a serve
task that builds the React app and serves it with
the Oak backend server. Run the following command to build and serve the app in
production mode:
deno run build
deno run serve
This 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
Visit localhost:8000
in your browser to see the production version of the app!
You can deploy this app to your favouite cloud provider. We recommend using Deno Deploy for a simple and easy deployment experience. You can deploy your app directly from GitHub, simply create a GitHub repository and push your code there, then connect it to Deno Deploy.
Create a GitHub repository Jump to heading
Create a new GitHub repository, then initialize and push your app to GitHub:
git init -b main
git remote add origin https://github.com/<your_github_username>/<your_repo_name>.git
git add .
git commit -am 'my next app'
git push -u origin main
Deploy to Deno Deploy Jump to heading
Once your app is on GitHub, you can deploy it on the Deno DeployEA dashboard. Deploy my app
For a walkthrough of deploying your app, check out the Deno Deploy tutorial.
π¦ Now you can scaffold and develop a React app with Vite and Deno! Youβre ready to build blazing-fast web applications. We hope you enjoy exploring these cutting-edge tools, we can't wait to see what you make!