Skip to content

Static Files

Shokupan provides built-in support for serving static files with optional directory listing.

Serve files from a directory:

import { Shokupan } from 'shokupan';
const app = new Shokupan();
// Serve static files from ./public
app.static('/public', {
root: './public'
});
app.listen();

Now files in ./public are accessible:

  • ./public/style.csshttp://localhost:3000/public/style.css
  • ./public/script.jshttp://localhost:3000/public/script.js
  • ./public/images/logo.pnghttp://localhost:3000/public/images/logo.png

Enable directory browsing:

app.static('/files', {
root: './files',
listDirectory: true // Enable directory listing
});

When you visit http://localhost:3000/files/, you’ll see a list of files and subdirectories.

Serve from multiple directories:

// Public assets
app.static('/public', {
root: './public',
listDirectory: true
});
// Images
app.static('/images', {
root: './assets/images',
listDirectory: false
});
// JavaScript bundles
app.static('/js', {
root: './dist/js',
listDirectory: false
});
// CSS stylesheets
app.static('/css', {
root: './dist/css',
listDirectory: false
});

Serve files at the root path:

// Serve from root
app.static('/', {
root: './public'
});
// Now accessible at:
// ./public/index.html → http://localhost:3000/index.html
my-app/
├── public/
│ ├── index.html
│ ├── favicon.ico
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── app.js
│ └── images/
│ └── logo.png
└── src/
└── index.ts

Configuration:

app.static('/public', {
root: './public',
listDirectory: true
});
// Or serve at root for SPA
app.static('/', {
root: './public'
});

For SPAs, serve your build directory and handle client-side routing:

import { Shokupan } from 'shokupan';
const app = new Shokupan();
// API routes
app.get('/api/users', (ctx) => ({ users: [] }));
app.get('/api/posts', (ctx) => ({ posts: [] }));
// Serve static files
app.static('/', {
root: './dist'
});
// Fallback to index.html for client-side routing
app.get('/*', (ctx) => {
return ctx.file('./dist/index.html', {
type: 'text/html'
});
});
app.listen();

Shokupan automatically sets the correct Content-Type header based on file extension:

  • .htmltext/html
  • .csstext/css
  • .jsapplication/javascript
  • .jsonapplication/json
  • .pngimage/png
  • .jpg, .jpegimage/jpeg
  • .svgimage/svg+xml
  • .pdfapplication/pdf
  • And many more…
// ✅ Safe - specific public directory
app.static('/public', {
root: './public',
listDirectory: true
});
// ❌ Dangerous - entire project
app.static('/', {
root: '.',
listDirectory: true
});

Add cache control headers:

const staticMiddleware = async (ctx, next) => {
const result = await next();
// Add cache headers for static assets
if (ctx.path.startsWith('/public')) {
ctx.set('Cache-Control', 'public, max-age=31536000');
}
return result;
};
app.use(staticMiddleware);
app.static('/public', {
root: './public'
});

Handle missing static files:

app.static('/files', {
root: './files',
listDirectory: true
});
// Fallback for 404
app.get('/files/*', (ctx) => {
return ctx.html('<h1>File Not Found</h1>', 404);
});

Different configurations for environments:

const isDev = process.env.NODE_ENV !== 'production';
app.static('/public', {
root: './public',
listDirectory: isDev // Only in development
});
if (isDev) {
// Development-specific static routes
app.static('/docs', {
root: './docs',
listDirectory: true
});
}