├── .gitignore ├── README.md ├── bun.lockb ├── package.json ├── src ├── components │ └── Counter.tsx ├── hydrate.tsx ├── index.tsx └── pages │ └── index.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | **/*.trace 37 | **/*.zip 38 | **/*.tar.gz 39 | **/*.tgz 40 | **/*.log 41 | package-lock.json 42 | **/*.bun 43 | 44 | # SSR build 45 | public -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elysia with Bun runtime 2 | 3 | ## Getting Started 4 | To get started with this template, simply paste this command into your terminal: 5 | ```bash 6 | bun create elysia ./elysia-example 7 | ``` 8 | 9 | ## Development 10 | To start the development server run: 11 | ```bash 12 | bun run dev 13 | ``` 14 | 15 | Open http://localhost:3000/ with your browser to see the result. 16 | 17 | ## React Server Side Rendering 18 | 19 | Based in this work: 20 | 21 | - https://github.com/bun-community/create-templates/tree/main/react-ssr 22 | 23 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midudev/bun-deploy-app/6760ff9f81e0c5975785653c012c0c637e294669/bun.lockb -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@elysiajs/html": "0.6.5", 4 | "@elysiajs/static": "0.6.0", 5 | "@elysiajs/swagger": "0.6.2", 6 | "@types/react": "18.2.21", 7 | "elysia": "latest", 8 | "react": "18.2.0", 9 | "react-dom": "18.2.0" 10 | }, 11 | "devDependencies": { 12 | "@types/react-dom": "18.2.7", 13 | "bun-types": "latest" 14 | }, 15 | "module": "src/index.js", 16 | "name": "hi-bun-api", 17 | "scripts": { 18 | "dev": "bun run --watch src/index.tsx", 19 | "build:hydrator": "bun build --minify --target=browser --outdir=public src/public/main.tsx", 20 | "test": "echo \"Error: no test specified\" && exit 1" 21 | }, 22 | "version": "1.0.50" 23 | } -------------------------------------------------------------------------------- /src/components/Counter.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | export const Counter = () => { 4 | const [count, setCount] = useState(0); 5 | 6 | const increment = () => setCount(count + 1); 7 | const decrement = () => setCount(count - 1); 8 | 9 | return ( 10 |
11 |

Count: {count}

12 | 13 | 14 |
15 | ) 16 | } -------------------------------------------------------------------------------- /src/hydrate.tsx: -------------------------------------------------------------------------------- 1 | import { hydrateRoot } from 'react-dom/client'; 2 | const { default: App} = await import(globalThis.__CLIENT_COMPONENT_SRC__) 3 | 4 | hydrateRoot(document, ); -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { Elysia, t } from "elysia"; 2 | import { staticPlugin } from '@elysiajs/static'; 3 | import { swagger } from '@elysiajs/swagger'; 4 | import { Counter } from "./components/Counter"; 5 | import { renderToReadableStream } from 'react-dom/server'; 6 | import { basename } from "path"; 7 | import App from "./pages"; 8 | import html from "@elysiajs/html"; 9 | 10 | const {outputs: [hydratratejsAsset, pageAsset]} = await Bun.build({ 11 | entrypoints: ['src/hydrate.tsx', 'src/pages/index.tsx'], 12 | outdir: 'public', 13 | target: 'browser', 14 | splitting: true, 15 | minify: true, 16 | publicPath: '/', 17 | }); // You can read automatically from outputs 18 | 19 | 20 | const TODOS = [ 21 | { id: 1, title: 'Buy milk' }, 22 | { id: 2, title: 'Buy eggs' }, 23 | { id: 3, title: 'Buy bread' } 24 | ] 25 | 26 | const app = new Elysia() 27 | .use(html()) 28 | .use(swagger()) 29 | .use(staticPlugin({ 30 | assets: 'public', 31 | prefix: '' 32 | })) 33 | .get("/", () => "Hello Elysia", { 34 | response: t.String() 35 | }) 36 | .get('/todos', () => TODOS, { 37 | detail: { 38 | summary: 'Return all TODOs for the user', 39 | tags: ['todos'] 40 | }, 41 | response: t.Array(t.Object({ 42 | id: t.Number(), 43 | title: t.String() 44 | })) 45 | }) 46 | .get('/todos/:id', ({ params: { id } }) => { 47 | return TODOS.find(todo => todo.id === +id) 48 | }, { 49 | params: t.Object({ 50 | id: t.Numeric() // Cast the type to Number, you can also use tranform object 51 | }), 52 | }) 53 | 54 | // This will work only if you use server-side apis only if none you should return html with react and do hydration or something =) 55 | .get('/app', () => ) 56 | .get(basename(hydratratejsAsset.path), () => Bun.file(hydratratejsAsset.path)) 57 | .get('/page', async () => { 58 | const reactStream = await renderToReadableStream(, { 59 | bootstrapScriptContent: `globalThis.__CLIENT_COMPONENT_SRC__ = "/pages/${basename(pageAsset.path)}"`, 60 | bootstrapModules: [basename(hydratratejsAsset.path)], 61 | onError: console.error 62 | }); 63 | return new Response(reactStream, { 64 | headers: { // Not necessary but be explicit in some cases it's fine 65 | 'Content-Type': 'text/html' 66 | } 67 | }) 68 | }) 69 | .listen(Bun.env.PORT || 0, ({ hostname, port}) => { 70 | console.log('Elysia started at http://%s:%s', hostname, port); 71 | }); 72 | 73 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { Counter } from "../components/Counter"; 2 | 3 | export default function IndexPage() { 4 | return ( 5 | 6 | 7 | 8 | 9 | {/* */} 10 | My app 11 | 12 | 13 |

This is a SSR component with a client side component inside

14 | 15 | 16 | 17 | ); 18 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 4 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 5 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 6 | /* Type Checking */ 7 | "jsx": "react-jsx", 8 | "module": "ES2022", /* Specify what module code is generated. */ 9 | // "rootDir": "./", /* Specify the root folder within your source files. */ 10 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 11 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 12 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 13 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 14 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 15 | "skipLibCheck": true, /* Skip type checking all .d.ts files. */ 16 | "strict": true, /* Enable all strict type-checking options. */ 17 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 18 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 19 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 20 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 21 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 22 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 23 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 24 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 25 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 26 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 27 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 28 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 29 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 30 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 31 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 32 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 33 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 34 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 35 | /* Completeness */ 36 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 37 | /* Visit https://aka.ms/tsconfig to read more about this file */ 38 | /* Projects */ 39 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 40 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 41 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 42 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 43 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 44 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 45 | /* Language and Environment */ 46 | "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 47 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 48 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 49 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 50 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 51 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 52 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 53 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 54 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 55 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 56 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 57 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 58 | /* Modules */ 59 | "types": [ 60 | "bun-types" 61 | ] /* Specify type package names to be included without being referenced in a source file. */ 62 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 63 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 64 | // "resolveJsonModule": true, /* Enable importing .json files. */ 65 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 66 | /* JavaScript Support */ 67 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 68 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 69 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 70 | /* Emit */ 71 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 72 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 73 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 74 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 75 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 76 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 77 | // "removeComments": true, /* Disable emitting comments. */ 78 | // "noEmit": true, /* Disable emitting files from a compilation. */ 79 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 80 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 81 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 82 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 83 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 84 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 85 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 86 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 87 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 88 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 89 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 90 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 91 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 92 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 93 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 94 | /* Interop Constraints */ 95 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 96 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 97 | } 98 | } --------------------------------------------------------------------------------