ejsdenooak

No such file or directory (os error 2), stat for rendering public files in oak


I am trying out Deno (v1.16.4) with Oak (v10) and ejs for the first time. The tailwind file is just a regular CSS file, so no installs. I got stuck serving a static CSS file. I must be overlooking something.

When I go to localhost:8080/tailwind.css, it'll show the file contents, but when going to localhost:8080/admin, it won't show No such file or directory (os error 2), stat '/Users/XXXXXXXX/Documents/projectNameHere/public/admin'. I've tried adjusting root and removing "/public" but now the tailwind file or the html files aren't found.

However, it doesn't seem to be ejs files because removing the first app.use() will display the admin pages (without styles, of course). So those are working.

This is the app.js for app entry.

import { Application } from "https://deno.land/x/oak/mod.ts";
import { route_home } from "./routes/home.js";
import { route_login } from "./routes/login.js";
import { route_admin } from "./routes/admin.js";
import { route_project } from "./routes/projectSingle.js";
import { route_about } from "./routes/about.js";
import { route_404 } from "./routes/404.js";

const app = new Application();
const port = 8080;

app.use(async (ctx) => {
  console.log(ctx.request.url.pathname);
  await send(ctx, ctx.request.url.pathname, {
    root: `${Deno.cwd()}/public`,
  });
});
app.use(route_home.routes());
app.use(route_login.routes());
app.use(route_admin.routes());
app.use(route_project.routes());
app.use(route_about.routes());
app.use(route_404.routes());

await app.listen({ port: port });

This is the admin.js for Admin routes.

import { Router } from "https://deno.land/x/oak/mod.ts";
import { renderFile } from "https://deno.land/x/dejs@0.10.2/mod.ts";

const router = new Router();

export const route_admin = router
  .get("/admin", async (ctx) => {
    console.log(ctx);
    const body = renderFile(Deno.cwd() + "/views/adminDashboard.ejs");
    ctx.response.body = await body;
  })
  .get("/admin/project-list", async (ctx) => {
    const body = renderFile(Deno.cwd() + "/views/adminProjectsList.ejs");
    ctx.response.body = await body;
  })
  .get("/admin/project/:id", async (ctx) => {
    const body = renderFile(Deno.cwd() + "/views/adminProjectSingle.ejs");
    ctx.response.body = await body;
  })
  .get("/admin/edit-other-pages", async (ctx) => {
    const body = renderFile(Deno.cwd() + "/views/adminOtherPages.ejs");
    ctx.response.body = await body;
  })
  .get("/admin/user-account", async (ctx) => {
    const body = renderFile(Deno.cwd() + "/views/adminUserAccount.ejs");
    ctx.response.body = await body;
  });

Any thoughts?

Updated Code

app.use(async (ctx, next) => {
  try {
    const dirPath = `${Deno.cwd()}/public`;
    if (existsSync(dirPath)) {
      console.log("yes, dir exists");
      for await (const file of Deno.readDir(dirPath)) {
        console.log("file: ", file);
      }
      await send(ctx, ctx.request.url.pathname, {
        root: `${Deno.cwd()}/public`,
      });
    }

    await next();
  } catch (error) {
    console.log("error: ", error);
  }
});

file structure:

this now gives me the following error:

[uncaught application error]: Http - error from user's HttpBody stream: body write aborted

    at async HttpConn.nextRequest (deno:ext/http/01_http.js:67:23)
    at async serve (https://deno.land/x/oak@v10.1.0/http_server_native.ts:237:34)
[uncaught application error]: Http - error from user's HttpBody stream: body write aborted

and in the chrome console:

Failed to load resource: the server responded with a status of 404 (Not Found)
Failed to load resource: the server responded with a status of 404 (Not Found)

Solution

  • I'll give it a try! I think your first middleware will try to serve static files on any request. You need a way to distinguish your static files from the rest of your app. For instance, you could try to put your tailwind find at /public/tailwind.css instead of tailwind.css. Then you can rewrite your middleware this way:

    app.use(async (ctx, next) => {
      if (ctx.request.url.pathname.startsWith('/public')) {
        const filename = ctx.request.url.pathname.substring(7)
        await send(ctx, filename, { root: 'public'})
      } else {
        await next()
      }
    })
    

    This implementation checks that the file should be served as static, then serves it without the prefix (otherwise it would try to find /public/public/tailwind.css), but other wise you need to call next to pass to the next middleware, here the routers middlewares