<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: János K.</title>
    <description>The latest articles on DEV Community by János K. (@janoskocs).</description>
    <link>https://dev.to/janoskocs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1264428%2F29706365-4596-4df0-abde-8105ffad360a.jpg</url>
      <title>DEV Community: János K.</title>
      <link>https://dev.to/janoskocs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/janoskocs"/>
    <language>en</language>
    <item>
      <title>Setting Up a Modern Express API with TypeScript, Jest, ESLint, Prettier - Quick guide 2025</title>
      <dc:creator>János K.</dc:creator>
      <pubDate>Sat, 17 May 2025 11:42:22 +0000</pubDate>
      <link>https://dev.to/janoskocs/setting-up-a-modern-express-api-with-typescript-jest-eslint-prettier-quick-guide-2025-1a26</link>
      <guid>https://dev.to/janoskocs/setting-up-a-modern-express-api-with-typescript-jest-eslint-prettier-quick-guide-2025-1a26</guid>
      <description>&lt;p&gt;Building a backend API in Node.js? Setting up the project can be a bit of a slog - and enough to put you off before you even start building that next-gen social media app.&lt;/p&gt;

&lt;p&gt;I couldn't help but wonder: where are the quick, modern guides to spinning up a well-structured Express API?&lt;/p&gt;

&lt;p&gt;There aren’t many. So I made one.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ZsxPSXH6k98"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Let’s get straight to it.&lt;/p&gt;

&lt;p&gt;🛠 1. Initialise your project&lt;br&gt;
&lt;code&gt;npm init -y&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This sets up your &lt;strong&gt;package.json&lt;/strong&gt;, which manages your dependencies, scripts, and project metadata. The -y flag accepts all the default values so you can move on quickly.&lt;/p&gt;

&lt;p&gt;📦 2. Install Express and dev dependencies&lt;br&gt;
&lt;code&gt;npm install express&lt;/code&gt;&lt;br&gt;
&lt;code&gt;npm install --save-dev typescript supertest nodemon jest ts-jest ts-node @types/jest @types/supertest @types/express&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What these do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;express: Your HTTP server framework.&lt;/li&gt;
&lt;li&gt;typescript: For strongly typed JS.&lt;/li&gt;
&lt;li&gt;supertest: For testing your API endpoints.&lt;/li&gt;
&lt;li&gt;jest, ts-jest: Testing framework and TypeScript preprocessor.&lt;/li&gt;
&lt;li&gt;nodemon: Restarts the server on changes.&lt;/li&gt;
&lt;li&gt;ts-node: Runs TypeScript files directly.&lt;/li&gt;
&lt;li&gt;@types/...: Adds type support for testing and Express.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🧠 3. Configure TypeScript&lt;br&gt;
&lt;code&gt;npx tsc --init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now replace your generated &lt;strong&gt;tsconfig.json&lt;/strong&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "exclude": [
    "./coverage",
    "./dist",
    "**/*.test.ts",
    "jest.config.js",
    "eslint.config.mjs"
  ],
  "ts-node": {
    "transpileOnly": true,
    "files": true
  },
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "rootDir": "./src",
    "moduleResolution": "node",
    "checkJs": true,
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitAny": true,
    "skipLibCheck": true
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The linter may freak out, until you create your first .ts file, which we will do now. ;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;🧱 4. Create your Express app&lt;br&gt;
Here's the directory structure for our app:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir -p src/routes &amp;amp;&amp;amp; touch src/app.ts src/app.test.ts src/server.ts src/routes/user.routes.ts src/routes/user.routes.test.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── app.ts
├── app.test.ts
├── server.ts
└── routes/
        └── user.routes.ts
        └── user.routes.test.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;src/server.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import app from './app';

const PORT: number = 5050;

app.listen(PORT, (): void =&amp;gt; {
  // eslint-disable-next-line no-console
  console.log(`Server is running on ${PORT}...`);
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;src/app.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import express, { Application, Request, Response} from 'express';
import { router as userRoutes } from './routes/user.routes';

const app: Application = express();

app.use('/users', userRoutes);

app.use('/', (req: Request, res: Response): void =&amp;gt; {
  res.json({ message: "Miley, what's good?" });
})

export default app;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;src/routes/user.routes.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Router, Request, Response } from 'express';

const router = Router();

router.get('/', (req: Request, res: Response): void =&amp;gt; {
  const users = ['Nicki', 'Ariana', 'Lana', 'Miley'];
  res.status(200).send(users);
});

export { router };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧪 5. Set up testing&lt;br&gt;
&lt;code&gt;npx ts-jest config:init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Replace the generated &lt;strong&gt;jest.config.js&lt;/strong&gt;'s contents with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const preset = 'ts-jest'
export const testEnvironment = 'node'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets up Jest to use the TypeScript compiler.&lt;/p&gt;

&lt;p&gt;🧷 6. Add some tests&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;src/app.test.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import request from 'supertest';
import app from './app';

describe('Test app.ts', () =&amp;gt; {
  test('Is alive route', async () =&amp;gt; {
    const res = await request(app).get('/');
    expect(res.body).toEqual({ message: "Miley, what's good?" });
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;src/routes/user.routes.test.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import request from 'supertest';
import app from '../app';

describe('User routes', () =&amp;gt; {
  test('Get all users', async () =&amp;gt; {
    const res = await request(app).get('/users');
    expect(res.body).toEqual(['Nicki', 'Ariana', 'Lana', 'Miley']);
  });
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚙️ 7. Update &lt;strong&gt;package.json&lt;/strong&gt; scripts&lt;br&gt;
Update the scripts section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
  "test": "jest --coverage",
  "dev": "nodemon ./src/server.ts",
  "build": "tsc",
  "lint": "eslint src/**/*.ts",
  "lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Don't worry about the lint scripts for now, we will set this up in the following step.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While you're at it, remove &lt;code&gt;"type":"module"&lt;/code&gt; from &lt;strong&gt;package.json&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Give this a go: &lt;code&gt;npm run test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧹 8. Set up ESLint + Prettier&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install -D eslint @eslint/js @types/eslint__js typescript-eslint eslint-plugin-prettier eslint-config-prettier&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now create the ESLint config file &lt;code&gt;touch eslint.config.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import eslintPluginPrettier from 'eslint-plugin-prettier'
import prettier from 'eslint-config-prettier'

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    files: ['**/*.ts'],
    languageOptions: {
      parser: tseslint.parser,
      parserOptions: {
        project: './tsconfig.eslint.json',
        sourceType: 'module',
      },
    },
    plugins: {
      prettier: eslintPluginPrettier,
    },
    rules: {
      'no-unused-vars': 'error',
      'no-undef': 'off',
      'prefer-const': 'error',
      'no-console': 'warn',
      'no-debugger': 'warn',
      'prettier/prettier': [
        'error',
        {
          singleQuote: true,
          semi: true,
          trailingComma: 'all',
          printWidth: 100,
          tabWidth: 2,
        },
      ],
    },
  },
  prettier,
  {
    ignores: ['dist/**', 'node_modules/**', 'coverage/**'],
  },
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's also add TypeScript config for ESLint&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;touch tsconfig.eslint.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "extends": "./tsconfig.json",
  "include": ["src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules", "dist"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have:&lt;/p&gt;

&lt;p&gt;A strongly typed Express API 💪&lt;/p&gt;

&lt;p&gt;Tests with Jest + Supertest 🧪&lt;/p&gt;

&lt;p&gt;Auto formatting &amp;amp; linting with ESLint + Prettier ✨&lt;/p&gt;

&lt;p&gt;You’re all set to build something real - without battling the boilerplate every time.&lt;/p&gt;

&lt;p&gt;Thank you, please like and subscribe! 💾&lt;br&gt;
Voilà! If you enjoy this content, please consider supporting my efforts. Your generosity fuels more of what you love! 🩷&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/janoskocs" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtmxoj38py85hldbc1pt.png" alt="Buy me a coffee"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I'm János, I write about Software, coding, and technology.&lt;br&gt;
Checkout my &lt;a href="https://janoskocs.com" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;&lt;/p&gt;

</description>
      <category>express</category>
      <category>typescript</category>
      <category>eslint</category>
      <category>testing</category>
    </item>
    <item>
      <title>SSL Certificates for FREE—nginx AWS Route 53</title>
      <dc:creator>János K.</dc:creator>
      <pubDate>Sun, 01 Sep 2024 10:14:27 +0000</pubDate>
      <link>https://dev.to/janoskocs/ssl-certificates-for-free-nginx-aws-route-53-2fdi</link>
      <guid>https://dev.to/janoskocs/ssl-certificates-for-free-nginx-aws-route-53-2fdi</guid>
      <description>&lt;p&gt;In this post, I want to write about creating an SSL certificate for your AWS EC2 instance. Checkout my previous post on how to deploy your app to an EC2 instance and setting up a continuous deployment pipeline using GitHub actions. &lt;a href="https://dev.to/janoskocs/deploy-react-app-to-ec2-using-github-actions-a-love-story-29h1"&gt;Deploy React App to EC2 using GitHub Actions&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Topics covered in this article: nginx, AWS Route 53, Elastic IPs, Let's Encrypt&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Setting up Elastic IPs&lt;/li&gt;
&lt;li&gt;Hosted zones&lt;/li&gt;
&lt;li&gt;Certificates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="elastic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Elastic IPs
&lt;/h2&gt;

&lt;p&gt;We are going to set up an elastic IP to and associate it with our EC2 instance. This means consistency for us which is important when it comes to setting up DNS records. Even if you turn off your instance it will remain static. Very demure.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check out the docs on Elastic IPs including pricing!&lt;/em&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html" rel="noopener noreferrer"&gt;AWS Elastic IP&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to AWS EC2 dashboard, in the sidebar click on Elastic IPs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F265b66dtxk462uh2tuqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F265b66dtxk462uh2tuqn.png" alt="EC2 Sidebar" width="286" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Big orange button alert — &lt;strong&gt;Allocate Elastic IP address&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Leave everything on the default settings (or customise it to your liking)&lt;/li&gt;
&lt;li&gt;Allocate&lt;/li&gt;
&lt;li&gt;Highlight the EIP you just created and select Actions then Associate Elastic IPs
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwnuub9xs0b6v9tw03307.png" alt="Associate Elastic IPs" width="800" height="164"&gt;
&lt;/li&gt;
&lt;li&gt;Click on the Instance button and the Instance you created&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprxt9eabmohb3gj6x5ka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprxt9eabmohb3gj6x5ka.png" alt="Associate Elastic IP to EC2 instance" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Amazing work so far! You now should be able to access your EC2 instance via the elastic IP. Remember it is still &lt;strong&gt;http&lt;/strong&gt; in the URL. COPY YOUR ELASTIC IP TO YOUR CLIPBOARD, WE WILL NEED IT SOON.&lt;/p&gt;

&lt;p&gt;&lt;a id="hosted"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted zones
&lt;/h2&gt;

&lt;p&gt;These steps assume you purchased your domain on AWS.&lt;br&gt;
   If your domain is on a 3rd party provider consider looking at other &lt;br&gt;
   articles on how to update records to point them to the Elastic IPs. Then come back here!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search for Route 53 in AWS&lt;/li&gt;
&lt;li&gt;Click on hosted zones in your dashboard&lt;/li&gt;
&lt;li&gt;Select the hosted zone of your domain — if you can see your domain and its hosted zone then open it and skip to step 5. &lt;/li&gt;
&lt;li&gt;Big orange button alert — Create hosted zone&lt;/li&gt;
&lt;li&gt;Fill in your domain name and click Create
&lt;em&gt;Type should be Public hosted zone&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Big orange button alert — Create record -&amp;gt; Simple routing -&amp;gt; Next&lt;/li&gt;
&lt;li&gt;Define simple record - &lt;em&gt;we will define multiple records so don't be hasty to click on create records&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Select Routes traffic to an IPv4 address and some AWS resources&lt;/li&gt;
&lt;li&gt;Paste your elastic IP in the input (Value/Route traffic to) — phew!&lt;/li&gt;
&lt;li&gt;Define simple record&lt;/li&gt;
&lt;li&gt;DON'T CLICK CREATE YET&lt;/li&gt;
&lt;li&gt;Define another simple record. We need that sweet www 🤓 
Like this:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzp828980cd64wp534o9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzp828980cd64wp534o9.png" alt="Define simple records in AWS" width="623" height="818"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final product:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrw8zy6hbc09izhce7jd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrw8zy6hbc09izhce7jd.png" alt="Simple records in AWS" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Big orange button alert — Create Records&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Optional: Please note if you delete and create a new hosted zone, then you must update the name servers in Route 53. Go to Registered Domains -&amp;gt; your-domain.com -&amp;gt; Edit name servers. You can copy the name servers from the records in hosted zones. It may take 24 hours to propagate these changes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You should now be able to view your site live. Remember, it is still http:// only. If you get an error, just remove the s from https. &lt;/p&gt;

&lt;p&gt;&lt;a id="certificates"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Certificates
&lt;/h2&gt;

&lt;p&gt;Now's the fun part! I mean this is what brings me joy, I don't know about you though.&lt;br&gt;
This is the part where we will edit the nginx config file so that it will be aware of our domain name's existence. Certbot will inject the SSL related configurations automatically later on. We will make use of &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let's encrypt&lt;/a&gt; to provide us a free SSL certificate.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect to your EC2 instance via the terminal&lt;/li&gt;
&lt;li&gt;In the terminal &lt;code&gt;cd /etc/nginx/sites-available/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then &lt;code&gt;sudo cp default backupdefault&lt;/code&gt; — quick backup, you never know! 🤭&lt;/li&gt;
&lt;li&gt;To edit the config file, type in &lt;code&gt;sudo nano default&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scroll to this part:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo553wfdb3w87x7bhy4gk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo553wfdb3w87x7bhy4gk.png" alt="nginx default configuration" width="566" height="251"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Type in your domain like below:&lt;br&gt;
&lt;code&gt;server_name your-domain.com www.your-domain.com;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your config file should like this:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy5c47u2oznvvnatjkf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy5c47u2oznvvnatjkf6.png" alt="Final nginx configuration" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exit, remember to write your changes aka save!&lt;/li&gt;
&lt;li&gt;Test the config file &lt;code&gt;sudo nginx -t&lt;/code&gt; =&amp;gt; you should see test successful&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IMPORTANT&lt;/strong&gt; run &lt;code&gt;sudo service nginx restart&lt;/code&gt; as this ensures the new config settings are loaded&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Let's encrypt&lt;/strong&gt; 🔒 — getting that sweet https 🍭&lt;/p&gt;

&lt;p&gt;Run the following commands&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;sudo snap install core; sudo snap refresh core&lt;/code&gt; — installs Snap package manager so that we can install Certbot&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo apt remove certbot&lt;/code&gt; — remove existing Certbot just in case and to avoid any conflicts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo snap install --classic certbot&lt;/code&gt; — classic is what we need&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo ln -s /snap/bin/certbot /usr/bin/certbot&lt;/code&gt; — this ensures that Certbot command is accessible from anywhere on the system&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo systemctl reload nginx&lt;/code&gt; — reload nginx again for the last time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Obtaining Free SSL Certificate&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;run &lt;code&gt;sudo certbot --nginx -d your-domain.com -d www.your-domain.com&lt;/code&gt;
&lt;em&gt;Replace your-domain with your-domain&lt;/em&gt; 🤭
Here you can add multiple subdomains as well just add the -d flag.&lt;/li&gt;
&lt;li&gt;It will ask for your email address for renewals&lt;/li&gt;
&lt;li&gt;Read the &lt;em&gt;Terms Of Service&lt;/em&gt; and agree if you'd like to proceed&lt;/li&gt;
&lt;li&gt;Next, it will ask about marketing campaigns; this is optional&lt;/li&gt;
&lt;li&gt;You should see a success message at the end&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6g9lbeyrlw8xdqjetpw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6g9lbeyrlw8xdqjetpw.png" alt="Success SSL certificate" width="800" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run this if you want auto SSL renewal (bet you do)
&lt;code&gt;sudo systemctl status snap.certbot.renew.service&lt;/code&gt;
You should see this message:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rx2429ggu2mvesk325c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rx2429ggu2mvesk325c.png" alt="Renewal active" width="648" height="74"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if the renewal process works by running:
&lt;code&gt;sudo certbot renew --dry-run&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thank you, please like and subscribe! 💾&lt;br&gt;
Voilà! If you enjoy this content, please consider supporting my efforts. Your generosity fuels more of what you love! 🩷&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/janoskocs" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtmxoj38py85hldbc1pt.png" alt="Buy me a coffee" width="434" height="100"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I'm János, I write about Software, coding, and technology.&lt;br&gt;
Checkout my &lt;a href="https://janoskocs.com" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ssl</category>
      <category>aws</category>
      <category>nginx</category>
      <category>letsencrypt</category>
    </item>
    <item>
      <title>Setting up a React project using Vite + TypeScript + Vitest</title>
      <dc:creator>János K.</dc:creator>
      <pubDate>Fri, 19 Apr 2024 18:39:55 +0000</pubDate>
      <link>https://dev.to/janoskocs/setting-up-a-react-project-using-vite-typescript-vitest-2gl2</link>
      <guid>https://dev.to/janoskocs/setting-up-a-react-project-using-vite-typescript-vitest-2gl2</guid>
      <description>&lt;p&gt;This is a quick step-by-step tutorial designed to guide you through setting up the project. While I strive to provide detailed explanations, feel free to skip ahead and simply follow each step; you should still achieve the desired results.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/FGA7djd7hDs"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up React Project with Vite
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;npm create vite &amp;lt;project-title&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;React&lt;/code&gt; -&amp;gt; &lt;code&gt;TypeScript&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm install&lt;/code&gt; and &lt;code&gt;npm run dev&lt;/code&gt; to start a local development server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fezfw57t2j3m4dkaffldo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fezfw57t2j3m4dkaffldo.png" alt="React Vite Set up" width="616" height="234"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxynxps7wek6dod7skqh2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxynxps7wek6dod7skqh2.png" alt="React Vite Set up" width="588" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Vitest
&lt;/h2&gt;

&lt;p&gt;Vitest test runner is optimised for Vite-powered projects. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;npm install -D vitest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm install -D @testing-library/react @testing-library/jest-dom jsdom&lt;/code&gt;&lt;br&gt;
First package, &lt;em&gt;@testing-library/react&lt;/em&gt;, is for component testing, second, &lt;em&gt;@testing-library/jest-dom&lt;/em&gt;,  is for DOM assertions, and lastly, &lt;em&gt;jsdom&lt;/em&gt;, is for simulating a browser environment in Node for testing purposes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update &lt;strong&gt;&lt;em&gt;vite.config.ts&lt;/em&gt;&lt;/strong&gt; to include testing configurations&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// &amp;lt;reference types="vitest" /&amp;gt;
/// &amp;lt;reference types="vite/client" /&amp;gt;

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/setupTests.ts'],
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changes we made:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added type definitions at the beginning of the file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;globals: true&lt;/code&gt; means global variables will be available during tests like 'describe, it, expect' so we don't have to import it in every test file&lt;/li&gt;
&lt;li&gt;Specified &lt;em&gt;'jsdom'&lt;/em&gt; as the test environment, simulating a browser environment&lt;/li&gt;
&lt;li&gt;Defined &lt;em&gt;'setupFiles'&lt;/em&gt; to execute necessary code before running the tests. This is a perfect segue to create &lt;em&gt;setupTests.ts&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create setupTests.ts
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;em&gt;setupTests.ts&lt;/em&gt; in the &lt;strong&gt;src&lt;/strong&gt; directory&lt;/li&gt;
&lt;li&gt;Paste this into &lt;em&gt;setupTests.ts&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';

expect.extend(matchers);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we bring Jest's DOM matchers into Vite, making testing feel more familiar and easier for users familiar with Jest.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;"test": "vitest"&lt;/code&gt; to &lt;em&gt;package.json&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fex4ro87jk7bz54y6z0y6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fex4ro87jk7bz54y6z0y6.png" alt="Test script in package.json" width="800" height="146"&gt;&lt;/a&gt;&lt;br&gt;
This will allow you to run your tests using &lt;code&gt;npm test&lt;/code&gt;.&lt;br&gt;
Onto, to our first test!&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing our first test
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Let's create a new file called &lt;em&gt;App.test.tsx&lt;/em&gt; in the &lt;strong&gt;src&lt;/strong&gt; folder.&lt;/li&gt;
&lt;li&gt;Here's a passing test for you to get started:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { screen, render } from "@testing-library/react";
import App from "./App";

describe("App tests", () =&amp;gt; {
  it("should render the title", () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    expect(
      screen.getByRole("heading", {
        level: 1,
      })
    ).toHaveTextContent("Vite + React");
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But oh wait! It's all red!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmrq2ddyyqznkueeizfk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmrq2ddyyqznkueeizfk.png" alt="App tests with errors" width="681" height="368"&gt;&lt;/a&gt;&lt;br&gt;
The tests will pass, we just need to tell TypeScript to include type definitions for Vitest global variables and Jest DOM matchers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;"types": ["vitest/globals", "@testing-library/jest-dom"],&lt;/code&gt; to &lt;em&gt;tsconfig.json&lt;/em&gt; in the &lt;code&gt;compilerOptions&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxo4e5arax5nrajy1tdae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxo4e5arax5nrajy1tdae.png" alt="Add Vitest types to TypeScript Config" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voilà! If you enjoy this content, please consider supporting my efforts. Your generosity fuels more of what you love! 🩷&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/janoskocs" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtmxoj38py85hldbc1pt.png" alt="Buy me a coffee" width="434" height="100"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I'm János, I write about Software, coding, and technology.&lt;br&gt;
Checkout my &lt;a href="https://janoskocs.com" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;. 💾&lt;/p&gt;

</description>
      <category>vite</category>
      <category>react</category>
      <category>typescript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deploy React App to EC2 using GitHub Actions—a love story</title>
      <dc:creator>János K.</dc:creator>
      <pubDate>Wed, 14 Feb 2024 19:38:21 +0000</pubDate>
      <link>https://dev.to/janoskocs/deploy-react-app-to-ec2-using-github-actions-a-love-story-29h1</link>
      <guid>https://dev.to/janoskocs/deploy-react-app-to-ec2-using-github-actions-a-love-story-29h1</guid>
      <description>&lt;p&gt;As my fingers danced on they keyboard the other week, I felt each line of code was destined to be hosted on an EC2 instance.&lt;br&gt;
I couldn't help but wonder, why companies love AWS so much? This is a tale of a modern romance between code and cloud. Think of it as a step by step guide to deploy a Vite React app using the unsung hero of our love story: GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a Vite app and the GitHub repository&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create vite@latest &amp;lt;project name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will ask you a few questions about your project. Select React -&amp;gt; JavaScript. &lt;/p&gt;

&lt;p&gt;We can start to see the difference between create-react-app and Vite already. Remember to run &lt;code&gt;npm i&lt;/code&gt; to install all dependencies and &lt;code&gt;git init&lt;/code&gt; to initialise your local repo. Hook this up with your remote repository, but remember to commit first! 💍&lt;br&gt;
And just like that, we say goodbye to create-react-app, our former flame.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Create an EC2 Instance on AWS&lt;/strong&gt;&lt;br&gt;
Next up is more fun! Let's create an EC2 instance, which is short for Elastic Compute; it is very flexible for our needs. AWS has a bunch of services that make this deployment easier like Elastic Beanstalk, but we are going to choose the rocky road because let's face it—it's just so much cooler. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your AWS console and click on the search bar and type in EC2 to search for this service&lt;/li&gt;
&lt;li&gt;Look for a big orange button called Launch Instance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Leave everything on the default settings except the following:&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give your instance a name; this could be your app's name for example&lt;/li&gt;
&lt;li&gt;Select Ubuntu as OS&lt;/li&gt;
&lt;li&gt;Remember to double check the prices, AWS gives you free months though which is helpful in this economy&lt;/li&gt;
&lt;li&gt;Create a new keypair using its default settings and save it to your desktop or somewhere safe and accessible&lt;/li&gt;
&lt;li&gt;Tick both allow HTTP/HTTPS tick boxes like on the image below:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbrv18l9ejqka3eul2n3x.png" alt="AWS security settings" width="800" height="342"&gt;
&lt;/li&gt;
&lt;li&gt;Launch instance!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;3.SSH into our virtual machine&lt;/strong&gt;&lt;br&gt;
This is the hacky part of the love story. We are finally logging into our virtual machine on the cloud. &lt;/p&gt;

&lt;p&gt;Go back the EC2 Instance Dashboard. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on the instance ID&lt;/li&gt;
&lt;li&gt;Click connect and then select SSH client&lt;/li&gt;
&lt;li&gt;Follow the steps in your terminal: you're looking for 2 commands: &lt;code&gt;chmod 400 "yourkey.pem"&lt;/code&gt; and copy the example command. Remember your terminal should be in the directory where you kept your key file! Never share this file with anyone!&lt;/li&gt;
&lt;li&gt;Let's run &lt;code&gt;sudo apt-get update&lt;/code&gt; to install the latest updates&lt;/li&gt;
&lt;li&gt;Install nginx by running &lt;code&gt;sudo apt-get install nginx&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pro tip: Don't &lt;code&gt;cd&lt;/code&gt; anywhere at this point! Your terminal will be in &lt;code&gt;/home/ubuntu/&lt;/code&gt;. The folder called ubuntu is the user's folder which doesn't require admin rights like &lt;code&gt;sudo&lt;/code&gt; when running commands to install GitHub Actions.&lt;/p&gt;

&lt;p&gt;Nginx is a web server software that will serve the production version of our app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Set up GitHub Actions&lt;/strong&gt;&lt;br&gt;
GitHub Actions is a godsend. We will set up a self-hosted runner which will run workflows and jobs on our EC2 instance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your repository settings&lt;/li&gt;
&lt;li&gt;Select runners 
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foc4k4dwzf39luxgy018m.png" alt="GitHub Actions runners" width="402" height="137"&gt;
&lt;/li&gt;
&lt;li&gt;Click on 'new self hosted runner'&lt;/li&gt;
&lt;li&gt;Remember to select Linux because our EC2 instance runs on Linux!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next steps will install GitHub actions runner on our virtual machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy and paste commands one by one from GitHub into our EC2 instance &lt;strong&gt;(except the last one &lt;code&gt;./run.sh&lt;/code&gt;!)&lt;/strong&gt; If you run the last command, just press Control + C to stop the process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Keep pressing enter to set the default settings when it prompts you.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2f342rwi75c1pp4dbmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2f342rwi75c1pp4dbmg.png" alt="GitHub Actions Terminal" width="719" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of the last command &lt;code&gt;./run.sh&lt;/code&gt;, we will install the runner as a &lt;a href="https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service" rel="noopener noreferrer"&gt;service&lt;/a&gt; to listen for jobs in the background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ./svc.sh install
sudo ./svc.sh start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this is done, you should refresh the GitHub page after a few minutes and check the self hosted runner is idle. Sometimes, it helps if you click on runners, and actions a few times before this comes up! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj14mz0wlb6udhz98hdu3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj14mz0wlb6udhz98hdu3.png" alt="Self Hosted Runner Idle" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Set up the workflow in the repository&lt;/strong&gt;&lt;br&gt;
In this step, we are going to set up the workflow; a bit like teaching EC2 how we operate.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on Actions (next to the Pull requests button) and add a new workflow&lt;/li&gt;
&lt;li&gt;Let's search for a Node workflow and select configure&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This will create a new folder and a file in your project &lt;code&gt;.github/workflows/node.js.yml&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;In this .yml file:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I removed the pull request job&lt;/li&gt;
&lt;li&gt;I updated &lt;strong&gt;runs-on: self-hosted&lt;/strong&gt; This means that the job will run on our EC2 instance as we set up earlier instead on a throw away like instance that GitHub provides.&lt;/li&gt;
&lt;li&gt;I updated the node version to &lt;code&gt;node-version: [20.x]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;I removed the test command at the very end&lt;/li&gt;
&lt;li&gt;I added 2 &lt;strong&gt;important&lt;/strong&gt; jobs:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Clean up the /var/www/html/ folder. Nginx creates these folders and uses them serve our static files. We will remove the default files.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Clear contents of /var/www/html
  run: sudo rm -rf /var/www/html/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Copy the production files from /home/ubuntu... to 
/var/www/html
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Copy dist folder to /var/www/html
  run: sudo cp -r dist/* /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Just replace it with mine here:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Node.js CI

on:
  push:
    branches: [ "main" ]

jobs:
  build:

    runs-on: self-hosted

    strategy:
      matrix:
        node-version: [20.x]

    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npm ci
    - run: npm run build --if-present
    - name: Clear contents of /var/www/html
      run: sudo rm -rf /var/www/html/*
    - name: Copy dist folder to /var/www/html
      run: sudo cp -r dist/* /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Commit this file&lt;/strong&gt; by clicking on the top right corner button which will trigger the workflow and nginx will serve the production version of the app. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click on the Actions tab again to watch magic happen!&lt;/p&gt;

&lt;p&gt;Remember to &lt;code&gt;git pull&lt;/code&gt; in your local repository as we committed the new workflow file to avoid divergent branches! After all, The love story shouldn't end on divergent branches. 💔&lt;/p&gt;

&lt;p&gt;To check out the hosted app by going to the EC2 instance details. Look for &lt;code&gt;Public IPv4 DNS&lt;/code&gt; and click on the link, but wait! Like all good things, it's wrapped in a layer of HTTPS we haven't quite set up yet. Fear not! &lt;strong&gt;Simply remove the 'S' from HTTPS in the URL&lt;/strong&gt;, and voilà!&lt;/p&gt;

&lt;p&gt;The URL should look something like: &lt;code&gt;http://ec2-35-177-142-238.eu-west-2.compute.amazonaws.com/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And just like that, our Vite app and EC2 are bound together. Whenever you push on main, the workflow kicks into action, redeploying our app to the world.&lt;/p&gt;

&lt;p&gt;This story isn't over, in the next chapter we will discover HTTPS and SSL certificates.&lt;br&gt;
Check it out here: &lt;a href="https://dev.to/janoskocs/ssl-certificates-for-free-nginx-aws-route-53-2fdi"&gt;SSL certificates&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you, please like and subscribe! 💾&lt;br&gt;
Voilà! If you enjoy this content, please consider supporting my efforts. Your generosity fuels more of what you love! 🩷&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/janoskocs" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtmxoj38py85hldbc1pt.png" alt="Buy me a coffee" width="434" height="100"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I'm János, I write about Software, coding, and technology.&lt;br&gt;
Checkout my &lt;a href="https://janoskocs.com" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cover photo by: &lt;a href="https://unsplash.com/@swimstaralex" rel="noopener noreferrer"&gt;Alexander Sinn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>aws</category>
      <category>github</category>
      <category>githubactions</category>
    </item>
  </channel>
</rss>
