Building a Dinosaur Runner Game with Deno: Part 1 - Project Setup and Deployment

web-development-tutorial

Learn to set up a basic Deno project for a browser-based dinosaur runner game. This guide covers project initialization, server configuration with Oak, serving static files, defining API routes, and deploying to Deno Deploy.

This multi-part series will guide you through the process of building a simple browser-based dinosaur runner game using Deno. Each installment corresponds to a distinct development stage, progressively introducing new features and essential concepts.

For the initial phase of our browser-based game, establishing a web server to host static files (HTML, CSS, JavaScript) is crucial. In this first part, we will configure a fundamental Deno project capable of serving these files locally and subsequently deploy it to the web using Deno Deploy.

In This Guide, You Will Learn How to:

  • Set up a basic Deno project.
  • Configure and run your Deno application.
  • Create a local web server for static files.
  • Define API routes.
  • Deploy your project to Deno Deploy.

Setting Up Your Deno Project

Setting up a basic Deno project is a straightforward process. You can initialize a new project folder with a deno.json configuration file using the deno init command:

deno init dino-runner

This command creates a new directory named dino-runner, which includes a deno.json file. This file will be used to define tasks and configurations specific to your Deno project.

Our goal is to construct a fundamental server capable of serving static files from a public/ directory, while also preparing for future API request handling.

Project Structure

Establish the following directory and file structure within your new project. Create any necessary empty files; we will populate them as we progress through this guide.

Runner Game/ ├── src/ # Server-side source code │ ├── main.ts # Server entry point │ └── routes/ # Route definitions │ └── api.routes.ts # API route definitions ├── public/ # Client-side static files │ ├── index.html # Main landing page & game canvas │ ├── js/ │ │ └── game.js # Client-side game logic │ └── css/ │ └── styles.css # Styling ├── deno.json # Deno configuration └── .env # Environment variables

Project Configuration

First, we need to install the necessary packages for this project. We'll utilize the Oak framework to facilitate server setup.

deno add jsr:@oak/oak

Next, we'll define some tasks within our deno.json file to simplify server management. Replace the existing tasks section in your deno.json with the following configuration:

{
  "tasks": {
    "dev": "deno run --allow-net --allow-read --allow-env --env-file --watch src/main.ts",
    "start": "deno run --allow-net --allow-read --allow-env --env-file src/main.ts"
  }
}

These tasks enable you to run the server in development mode with automatic file watching (deno task dev) and in production mode without watching (deno task start).

The --allow-net and --allow-read flags are crucial. By default, Deno operates with a secure sandboxed environment. These flags explicitly grant the server permission to listen for network requests and read static files from the filesystem, respectively.

The --env-file flag allows the server to access a .env file, which is invaluable for configuring host, port, and other environment-specific variables that will be used in later stages.

Now, let's configure some environment variables. Create or update your .env file in the project's root directory with the following:

PORT=8000
HOST=localhost

Basic HTML Structure (public/index.html)

Next, we will create a foundational index.html file within the public/ folder. This file will link to the assets we'll develop and use later.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <link rel="preload" href="https://demo-styles.deno.deno.net/fonts/Moranga-Regular.woff2" as="font" type="font/woff2" crossorigin/>
  <link rel="preload" href="https://demo-styles.deno.deno.net/fonts/Moranga-Medium.woff2" as="font" type="font/woff2" crossorigin/>
  <link rel="preload" href="https://demo-styles.deno.deno.net/fonts/Recursive_Variable.woff2" as="font" type="font/woff2" crossorigin/>
  <link rel="stylesheet" href="https://demo-styles.deno.deno.net/styles.css"/>
  <link rel="stylesheet" href="css/styles.css"/>
  <link rel="icon" href="favicon.ico" type="image/x-icon"/>
  <title>Dino Runner</title>
</head>
<body>
  <h1>Dino Runner Game - Stage 1</h1>
  <p>This is a placeholder for the Dino Runner game.</p>
  <script src="js/game.js" type="module"></script>
</body>
</html>

Implementing the Server (src/main.ts)

We will now implement a straightforward server using the Oak framework to efficiently serve static files located in the public/ directory.

import { Application } from "@oak/oak/application";

const PORT = parseInt(Deno.env.get("PORT") || "8001");
const HOST = Deno.env.get("HOST") || "localhost";

const app = new Application();

// Serve static files from public directory
app.use(async (context, next) => {
  try {
    await context.send({
      root: `${Deno.cwd()}/public`,
      index: "index.html",
    });
  } catch {
    await next();
  }
});

app.listen({ port: PORT });

console.log(`Server is running on http://${HOST}:${PORT}`);

This code initializes a local HTTP server that directly serves incoming requests from your /public folder. Deno.cwd() is used to retrieve the current working directory, ensuring that the server accurately locates the public/ folder irrespective of the script's execution location.

You can now run your server locally using the command:

deno task dev

Navigating to http://localhost:8000 in your web browser should display a basic web page similar to this:

Adding API Routes (src/routes/api.routes.ts)

Next, we will establish a fundamental routing structure for our API endpoints. While this file will remain relatively minimal for now, it lays the groundwork for subsequent development.

import { Router } from "@oak/oak/router";

export const apiRouter = new Router({
  prefix: "/api"
});

// Health check endpoint
apiRouter.get("/health", (ctx) => {
  ctx.response.body = {
    status: "ok",
    message: "🦕 Stage 1 - Dino server is healthy!"
  };
});

This code creates a basic health check endpoint at /api/health, allowing us to verify the server's operational status.

Now, we need to integrate this router into our main server file. Begin by adding an import statement for the apiRouter at the top of src/main.ts:

// src/main.ts
import { apiRouter } from "./routes/api.routes.ts";

Following the app.use middleware for static files, insert the following lines:

// src/main.ts
// API routes
app.use(apiRouter.routes());
app.use(apiRouter.allowedMethods());

These additions instruct the Oak application to utilize the routes defined within apiRouter.

With these changes, when you restart the server, you can access the health endpoint by navigating to http://localhost:8000/api/health in your browser. You should see a response similar to this:

Deploying Your Project to Deno Deploy

To share your newly created project with the world, let's deploy it to Deno Deploy.

First, ensure you have an active Deno Deploy account. If not, you can sign up at https://console.deno.com/.

Once your account is ready, use the deno deploy command from your project's root directory:

deno deploy

This command will initiate an interactive deployment process. You will be prompted to select a project name and your code will be uploaded to Deno Deploy. Crucially, ensure you select "Edit app config" and specify src/main.ts as the entry point for your application.

Upon successful deployment, you will receive a unique URL where your application is live.

Visit this URL in your browser to see your basic HTML page, now live and ready to be transformed into a full-fledged dinosaur runner game!

What's Next?

In the upcoming second part of this series, we will introduce enhanced features, focusing on increased interactivity, a more refined project structure, and the initial implementation of core game logic. Stay tuned for the next installment!