-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
11 changed files
with
193 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
{ | ||
"name": "fullstack-bun", | ||
"version": "1.0.0", | ||
"description": "Type-safe monorepo project template. Bun/Elysia backend, React frontend, Socket.IO bridging the gap.", | ||
"description": "Type-safe monorepo project template. Bun/Hono backend, React frontend, Socket.IO bridging the gap.", | ||
"author": "Chris Leveille <[email protected]>", | ||
"license": "MIT", | ||
"type": "module", | ||
|
@@ -35,11 +35,9 @@ | |
"workbox-strategies": "^7.3.0" | ||
}, | ||
"dependencies": { | ||
"@elysiajs/cors": "^1.1.1", | ||
"@elysiajs/static": "^1.1.1", | ||
"@elysiajs/swagger": "^1.1.5", | ||
"elysia": "^1.1.24", | ||
"elysia-helmet": "^2.0.0", | ||
"@hono/zod-openapi": "^0.16.4", | ||
"@scalar/hono-api-reference": "^0.5.158", | ||
"hono": "^4.6.8", | ||
"socket.io": "^4.8.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
import { t } from "elysia"; | ||
import { z } from "@hono/zod-openapi"; | ||
|
||
export const resMessageSchema = t.Object({ message: t.String() }); | ||
export const resMessageSchema = z.object({ message: z.string() }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,46 @@ | ||
import { Elysia, t, ValidationError } from "elysia"; | ||
import { helmet } from "elysia-helmet"; | ||
|
||
import { Env } from "@constants"; | ||
import { cors } from "@elysiajs/cors"; | ||
import { staticPlugin } from "@elysiajs/static"; | ||
import { swagger } from "@elysiajs/swagger"; | ||
import { Config, initSocket, resMessageSchema } from "@helpers"; | ||
|
||
import { name, version } from "../../package.json"; | ||
import { Config, initErrorHandling, initSocket } from "@helpers"; | ||
import { OpenAPIHono } from "@hono/zod-openapi"; | ||
import { initMiddleware } from "@middleware"; | ||
import { initRoutes } from "@routes"; | ||
|
||
const { IS_PROD, PORT, WS_PORT, HOST } = Config; | ||
const WS_HOST = Config.HOST.replace("http", "ws"); | ||
const { IS_PROD, PORT } = Config; | ||
|
||
const buildIfDev = IS_PROD ? [] : [(await import("@processes")).buildClient()]; | ||
|
||
await Promise.all([...buildIfDev, initSocket()]); | ||
|
||
const app = new Elysia() | ||
.onError(c => { | ||
if (c.error instanceof ValidationError) { | ||
return { | ||
message: c.error.all.map(e => e.summary).join(", ") | ||
}; | ||
} | ||
return { message: c.error?.message ?? "Internal Server Error" }; | ||
}) | ||
.use(cors()) | ||
.use( | ||
helmet({ | ||
contentSecurityPolicy: { | ||
directives: { | ||
defaultSrc: ["'self'"], | ||
baseUri: ["'self'"], | ||
childSrc: ["'self'"], | ||
connectSrc: ["'self'", `${HOST}:${WS_PORT}`, `${WS_HOST}:${WS_PORT}`], | ||
fontSrc: ["'self'", "https:", "data:"], | ||
formAction: ["'self'"], | ||
frameAncestors: ["'self'"], | ||
frameSrc: ["'self'"], | ||
imgSrc: ["'self'", "data:"], | ||
manifestSrc: ["'self'"], | ||
mediaSrc: ["'self'"], | ||
objectSrc: ["'none'"], | ||
scriptSrc: ["'self'"], | ||
scriptSrcAttr: ["'none'"], | ||
scriptSrcElem: ["*", "'unsafe-inline'"], | ||
styleSrc: ["'self'", "https:", "'unsafe-inline'"], | ||
styleSrcAttr: ["'self'", "https:", "'unsafe-inline'"], | ||
styleSrcElem: ["'self'", "https:", "'unsafe-inline'"], | ||
upgradeInsecureRequests: [], | ||
workerSrc: ["'self'", "blob:"] | ||
} | ||
} | ||
}) | ||
) | ||
.use(swagger({ path: "/reference", documentation: { info: { title: name, version } } })) | ||
.use(staticPlugin({ prefix: "/", assets: "./public" })) | ||
.get( | ||
"/hello", | ||
c => { | ||
const { name } = c.query; | ||
return { | ||
message: `hello ${name ? name : "world"}!` | ||
}; | ||
}, | ||
{ | ||
query: t.Object({ name: t.Optional(t.String()) }), | ||
response: resMessageSchema | ||
} | ||
) | ||
.post( | ||
"/hello", | ||
c => { | ||
const { name } = c.body; | ||
return { | ||
message: `hello ${name ? name : "world"}!` | ||
}; | ||
}, | ||
{ | ||
body: t.Object({ name: t.String() }), | ||
response: resMessageSchema | ||
const app = new OpenAPIHono({ | ||
defaultHook: (result, c) => { | ||
if (!result.success) { | ||
return c.json( | ||
{ | ||
message: result.error.errors | ||
.map(err => { | ||
const error = err as typeof err & { expected?: string }; | ||
const path = error.path.join("."); | ||
const message = error.message; | ||
const expected = error.expected ? ` <${error.expected}>` : ""; | ||
return `${path}${expected}: ${message}`; | ||
}) | ||
.join(", ") | ||
}, | ||
400 | ||
); | ||
} | ||
) | ||
}, | ||
strict: false | ||
}); | ||
|
||
initMiddleware(app); | ||
|
||
initRoutes(app); | ||
|
||
initErrorHandling(app); | ||
|
||
.listen(PORT); | ||
console.log(`HTTP server started on port ${PORT} in ${IS_PROD ? Env.Production : Env.Development} mode`); | ||
|
||
const url = app?.server?.url?.toString(); | ||
console.log(`HTTP server listening on ${url} in ${IS_PROD ? Env.Production : Env.Development} mode`); | ||
export default { | ||
port: Config.PORT, | ||
fetch: app.fetch | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { serveStatic } from "hono/bun"; | ||
import { cors } from "hono/cors"; | ||
import { secureHeaders } from "hono/secure-headers"; | ||
|
||
import { Path } from "@constants"; | ||
import { Config } from "@helpers"; | ||
import type { OpenAPIHono } from "@hono/zod-openapi"; | ||
import { apiReference } from "@scalar/hono-api-reference"; | ||
|
||
import { name, version } from "../../../package.json"; | ||
|
||
const { WS_PORT, HOST } = Config; | ||
const WS_HOST = Config.HOST.replace("http", "ws"); | ||
|
||
const openApiInfo = { | ||
openapi: "3.1.0", | ||
info: { version, title: name } | ||
}; | ||
|
||
export const initMiddleware = (app: OpenAPIHono) => { | ||
app.use(cors()); | ||
|
||
app.use( | ||
secureHeaders({ | ||
contentSecurityPolicy: { | ||
defaultSrc: ["'self'"], | ||
baseUri: ["'self'"], | ||
childSrc: ["'self'"], | ||
connectSrc: ["'self'", `${HOST}:${WS_PORT}`, `${WS_HOST}:${WS_PORT}`], | ||
fontSrc: ["'self'", "https:", "data:"], | ||
formAction: ["'self'"], | ||
frameAncestors: ["'self'"], | ||
frameSrc: ["'self'"], | ||
imgSrc: ["'self'", "data:"], | ||
manifestSrc: ["'self'"], | ||
mediaSrc: ["'self'"], | ||
objectSrc: ["'none'"], | ||
scriptSrc: ["'self'"], | ||
scriptSrcAttr: ["'none'"], | ||
scriptSrcElem: ["'self'", "https://cdn.jsdelivr.net/npm/@scalar/api-reference"], | ||
styleSrc: ["'self'", "https:", "'unsafe-inline'"], | ||
styleSrcAttr: ["'self'", "https:", "'unsafe-inline'"], | ||
styleSrcElem: ["'self'", "https:", "'unsafe-inline'"], | ||
upgradeInsecureRequests: [], | ||
workerSrc: ["'self'", "blob:"] | ||
} | ||
}) | ||
); | ||
|
||
app.get( | ||
"/*", | ||
serveStatic({ | ||
root: Path.Public, | ||
onFound: (_path, c) => c.header("Cache-Control", "no-store") | ||
}) | ||
); | ||
|
||
app.doc31("/spec", openApiInfo); | ||
app.getOpenAPI31Document(openApiInfo); | ||
|
||
app.get( | ||
"/reference", | ||
apiReference({ | ||
spec: { url: "/spec" } | ||
}) | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { resMessageSchema } from "@helpers"; | ||
import { createRoute, type OpenAPIHono, z } from "@hono/zod-openapi"; | ||
|
||
export const initHelloRoutes = (app: OpenAPIHono) => { | ||
app.openapi( | ||
createRoute({ | ||
method: "get", | ||
path: "/hello", | ||
request: { | ||
query: z.object({ name: z.string().optional() }) | ||
}, | ||
responses: { | ||
200: { | ||
content: { "application/json": { schema: resMessageSchema } }, | ||
description: "ok" | ||
} | ||
} | ||
}), | ||
c => { | ||
const { name } = c.req.valid("query"); | ||
const message = `Hello ${name ? name : "World"}!`; | ||
return c.json({ message }, 200); | ||
} | ||
); | ||
|
||
app.openapi( | ||
createRoute({ | ||
method: "post", | ||
path: "/hello", | ||
request: { | ||
body: { | ||
content: { "application/json": { schema: z.object({ name: z.string().min(1) }) } } | ||
} | ||
}, | ||
responses: { | ||
200: { | ||
content: { "application/json": { schema: resMessageSchema } }, | ||
description: "ok" | ||
}, | ||
400: { | ||
content: { "application/json": { schema: resMessageSchema } }, | ||
description: "bad request" | ||
} | ||
} | ||
}), | ||
c => { | ||
const body = c.req.valid("json"); | ||
const message = `Hello ${body.name || "World"}!`; | ||
return c.json({ message }, 200); | ||
} | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import type { OpenAPIHono } from "@hono/zod-openapi"; | ||
import { initHelloRoutes } from "@routes"; | ||
|
||
export * from "./hello"; | ||
|
||
export const initRoutes = (app: OpenAPIHono) => { | ||
initHelloRoutes(app); | ||
|
||
app.get("/health", c => c.text("OK")); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters