5

I am used to working with NodeJS and Koa. I've been playing with Deno and have run the example of a static fileserver:


/* static_server.js */

import { Application } from 'https://deno.land/x/oak/mod.ts'

const port  = 8080

const app = new Application()

// Error handler middleware
app.use(async (context, next) => {
  try {
    await next()
  } catch (err) {
        console.error(err)
    }
})

// Send static content
app.use(async (context) => {
    console.log(`${context.request.method} ${context.request.url.pathname}`)
  await context.send({
    root: `${Deno.cwd()}/static`,
    index: "index.html",
  })
})


await app.listen({ port })

I have also created a dynamic server using routes:


/* routes.js */

import { Application, Router } from 'https://deno.land/x/oak/mod.ts'

const port = 8080

const app = new Application()
const router = new Router()

router.get('/', context => {
    context.response.body = 'Hello world!'
  })

router.get('/foo', context => {
    context.response.body = 'Book Page'
  })

router.get('/foo/:thing', context => {
    context.response.body = `Foo ${context.params.thing}`
})

app.use(router.routes())
app.use(router.allowedMethods())

await app.listen({ port })

How can I combine these so that I can serve dynamic content but also provide static files such as the stylesheet?

In my Koa code I use the koa-static package:

import serve from 'koa-static'
app.use(serve('public'))

What is the equivalent for an Oak server?

Adding suggested code (thanks Jonas Wilms)


/* static_content.js */

import { Application, Router } from 'https://deno.land/x/oak/mod.ts'

const port = 8080

const app = new Application()
const router = new Router()

router.get('/', context => {
    context.response.body = 'Hello world!'
  })

router.get('/foo', context => {
    context.response.body = 'Book Page'
  })

router.get('/foo/:thing', context => {
    context.response.body = `Foo ${context.params.thing}`
})

router.get(context => context.send({ root: `${Deno.cwd()}/static` }))

app.use(router.routes())
app.use(router.allowedMethods())

await app.listen({ port })

but this still does not work...

Mark Tyers
  • 2,961
  • 4
  • 29
  • 52

5 Answers5

3

After combining a lot of the information in the comments I managed to get things working:


/* static_content.js */

import { Application, Router, Status } from 'https://deno.land/x/oak/mod.ts'

const port = 8080

const app = new Application()
const router = new Router()

// error handler
app.use(async (context, next) => {
  try {
    await next()
  } catch (err) {
        console.log(err)
  }
})

// the routes defined here
router.get('/', context => {
    context.response.body = 'Hello world!'
})

router.get('/error', context => {
    throw new Error('an error has been thrown')
})

app.use(router.routes())
app.use(router.allowedMethods())

// static content
app.use(async (context, next) => {
    const root = `${Deno.cwd()}/static`
    try {
        await context.send({ root })
    } catch {
        next()
    }
})

// page not found
app.use( async context => {
    context.response.status = Status.NotFound
  context.response.body = `"${context.request.url}" not found`
})

app.addEventListener("listen", ({ port }) => console.log(`listening on port: ${port}`) )

await app.listen({ port })


Mark Tyers
  • 2,961
  • 4
  • 29
  • 52
0

I know I'm a bit late on the thread, but there are some things I would like to point out.

In Oak 10.1 (the current version at the time of this writing), the send function throws an error if the file it tired to load did not exist. Thus, our static+dynamic server can take on the following form.

import { oak, pathUtils } from './deps.ts'

const app = new oak.Application()
const router = new oak.Router()

app.use(async (ctx, next) => {
    try {
        await oak.send(ctx, ctx.request.url.pathname, {
            root: 'static',
            index: 'index.html',
        })
    } catch (_) {
        await next()
    }
})

router.get('/dynamic', ctx => {
    ctx.response.body = 'dynamic route worked'
})

app.use(router.allowedMethods())
app.use(router.routes())

app.listen({ port: 8000 })

If you want to serve your static files at a certain root path, change the static middleware so that it checks for the root and then omits that root path from the second argument of the send function.

function staticMiddleware(rootPath: string, staticDirectory: string) {
    return async (ctx, next) => {
        if (!ctx.request.url.pathname.startsWith(rootPath)) return await next()

        const newPath = ctx.request.url.pathname.slice(rootPath.length)
        if (!newPath.startsWith('/') && newPath.length) return await next()

        try {
            await oak.send(ctx, newPath, {
                root: staticDirectory,
                index: 'index.html',
            })
        } catch (_) {
            await next()
        }
   }
}

app.use(staticMiddleware('/assets', 'static'))
Vehmloewff
  • 74
  • 1
  • 5
0

I think you should use the static router at last. Because when use static server first, dynamic router is nonreachable for static router error.

app.use(router.routes())
app.use(router.allowedMethods())
// move the static router down
app.use( async context => {
  context.response.status = Status.NotFound
  context.response.body = `"${context.request.url}" not found`
})
0

Not sure whether this is still relevant or already outdated, but as of now (August 2022), there seems to be no general answer to this.

Serving Static Files Alongside Your API Using Oak/Deno

When setting up OpenAPI for an oak-based REST service, I was coming across this issue as well. Requirements were:

  • Serve openapi.yml statically from /openapi/openapi.yml
  • Serve a HTML statically from /openapi for convenience
  • Serve prefixed routers unaffectedly

A straight-forward approach to serve static files from a certain directory under a sub-path of the application is using a middleware and checking the path:

import {
  Application, Context, Router
} from 'https://deno.land/x/oak@v11.1.0/mod.ts';

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

// Middleware only hooking in and sending static files if prefix matches
// the desired subpath:
const openapi = async (ctx: Context, next: () => Promise<unknown>) => {
  const prefix = '/openapi'; // Sub-path to react on
  if (ctx.request.url.pathname.startsWith(prefix)) {
    await ctx.send({
      root: `${Deno.cwd()}/src/openapi/`, // Local directory to serve from
      index: 'index.html',
      path: ctx.request.url.pathname.replace(prefix, ''), // Map to target path
    });
  } else {
    // If the request does not match the prefix, just continue serving from
    // whatever comes next..
    await next();
  }
};

// This is a dummy API endpoint wrapped into a prefixed router for demo:
const statusRouter = new Router({ prefix: '/status' });
statusRouter.get('/', (ctx: Context) => {
  ctx.response.body = {
    healthy: true,
    ready: true,
  };
});

// Boilerplate..
app.use(openapi);
app.use(statusRouter.routes());
app.use(statusRouter.allowedMethods());

app.addEventListener('listen', () => {
  console.log(`Listening on localhost:${port}`);
});

await app.listen({ port });

Running this MWE using deno run --allow-net --allow-env --allow-read src/serve.ts, you'll

  • find the statically served /openapi/openapi.yml,
  • find the index.html from your local static path served under /openapi (resp. /openapi/ and /openapi/index.html)
  • find the /status API behaving just normally.
jbndlr
  • 4,965
  • 2
  • 21
  • 31
-1

i'm using like that. In html you can provide a path to your file:

<script src="/js/app.js"></script>

then you can use routes to provide what do you want to use on path js/app.js:

import {RouterContext} from 'https://deno.land/x/oak/mod.ts'

const decoder = new TextDecoder("utf-8")// set doecoder
const fileCont = await Deno.readFile('./views/test.js') //getting file conetent
const fileJS = decoder.decode(fileCont) //decoding it

router.get('/js/app.js', (ctx: RouterContext) => { //yep, route can has defferents of real file location
        ctx.response.type = "application/javascript"
        ctx.response.body = fileJS
    })

and whatever you are providing this link somewhere it'll render you file.

Deno REST API

Morani
  • 446
  • 5
  • 15