Build a Production-Ready TypeScript App with Bun, ESLint, and VSCode Integration

Vincent Delacourt
3 min readJan 8, 2025

--

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. 🎉

--

--

Vincent Delacourt
Vincent Delacourt

Written by Vincent Delacourt

Interesting in start-up or project development in the latest technologies for web and mobile apps

No responses yet