Build a Production-Ready TypeScript App with Bun, ESLint, and VSCode Integration
In this tutorial, we’ll create a TypeScript app with Bun, configure robust linting with ESLint, and set up VSCode for a seamless development experience.
Step 1: Set Up a New Bun Project
First, ensure you have Bun installed. Then, initialize your project:
bun init
entry point (index.ts): src/index.ts
This creates a new Bun project with the basic setup, including a package.json
file.
Step 2: Install Dependencies
We’ll use several tools for this project:
- ESLint: For linting.
- Winston: For logging.
- ESLint plugins: To enhance linting for security, TypeScript, and more.
Install the required dependencies:
bun install --dev eslint @eslint/js typescript-eslint eslint-plugin-security eslint-plugin-prettier eslint-plugin-unicorn
bun install winston
Add to package.json
"scripts": {
"lint": "eslint"
}
Step 3: Configure ESLint
Create an eslint.config.js
file and add the following:
import pluginJs from '@eslint/js';
import prettier from 'eslint-plugin-prettier';
import securityPlugin from 'eslint-plugin-security';
import unicornPlugin from 'eslint-plugin-unicorn';
import globals from 'globals';
import tsPlugin from 'typescript-eslint';
/** @type {import('eslint').Linter.Config[]} */
export default [
// Security
securityPlugin.configs.recommended,
{
files: ['**/*.ts'],
},
{
languageOptions: { globals: globals.node },
},
{
rules: {
'func-style': ['error', 'expression'],
'no-restricted-syntax': ['off', 'ForOfStatement'],
'no-console': ['error'],
'prefer-template': 'error',
quotes: ['error', 'single', { avoidEscape: true }],
},
},
// TypeScript Eslint
{
rules: {
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
},
},
// Prettier
{
plugins: {
prettier,
},
rules: {
'prettier/prettier': [
1,
{
endOfLine: 'lf',
printWidth: 180,
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
},
],
},
},
// Unicorn
{
plugins: {
unicorn: unicornPlugin,
},
rules: {
'unicorn/empty-brace-spaces': 'off',
'unicorn/no-null': 'off',
},
},
pluginJs.configs.recommended,
...tsPlugin.configs.recommended,
];
This configuration:
- Enforces consistent coding styles.
- Adds TypeScript-specific rules.
- Includes security-related linting.
- Configures Prettier for formatting.
Step 4: Add VSCode Settings
For a seamless experience, configure VSCode to support ESLint and TypeScript. Create a .vscode/settings.json
file:
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.addMissingImports": "explicit",
"source.fixAll.eslint": "explicit"
},
"search.exclude": {
"**/node_modules": true,
"**/.vscode": true
},
"search.useGlobalIgnoreFiles": true,
"search.useParentIgnoreFiles": true,
"git.autofetch": true,
"editor.trimAutoWhitespace": true,
"files.encoding": "utf8",
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"editor.quickSuggestions": {
"strings": true
},
"editor.detectIndentation": false,
"editor.tabSize": 2,
"eslint.enable": true,
"eslint.format.enable": true,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnType": true,
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
}
And this is a list of extensions I recommend to install. Create a .vscode/extensions.json
file:
{
"recommendations": [
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"usernamehw.errorlens",
"lacroixdavid1.vscode-format-context-menu",
"kisstkondoros.vscode-codemetrics",
"snyk-security.snyk-vulnerability-scanner",
"sonarsource.sonarlint-vscode",
]
}
Step 5: Create a TypeScript Entry Point
Replace the content of src/index.ts
file:
import { createLogger, format, transports } from 'winston';
const logger = createLogger({
level: 'info',
format: format.json(),
transports: [new transports.Console()],
});
logger.info('Hello via Bun!');
Run the file to verify everything works:
bun run lint
bun run src/index.ts
Step 6: Add a Dockerfile
To containerize your app for production, create a Dockerfile
:
FROM oven/bun:1 AS base
WORKDIR /usr/src/app
# install dependencies into temp directory
FROM base AS install
RUN mkdir -p /temp/dev /temp/prod
COPY package.json bun.lockb /temp/dev/
WORKDIR /temp/dev
RUN bun install --frozen-lockfile
COPY package.json bun.lockb /temp/prod/
WORKDIR /temp/prod
RUN bun install --frozen-lockfile --production
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
# [optional] eslint (can also add test)
ENV NODE_ENV=production
RUN bun run lint
# copy production dependencies and source code into final image
FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /usr/src/app/src/ src/
COPY --from=prerelease /usr/src/app/package.json .
# run the app
USER bun
EXPOSE 8080/tcp
ENTRYPOINT [ "bun", "run", "src/index.ts" ]
Build and run the Docker image:
docker build -t bun-app .
docker run -p 8080:8080 bun-app
With this setup, you have a robust, production-ready Bun-based TypeScript application that adheres to modern TypeScript best practices. 🎉