{"id":21065800,"url":"https://github.com/washira/express-guides","last_synced_at":"2026-04-29T21:33:18.438Z","repository":{"id":228487164,"uuid":"774117371","full_name":"Washira/express-guides","owner":"Washira","description":"Advanced Express.js guides with multiple features","archived":false,"fork":false,"pushed_at":"2024-03-23T04:18:01.000Z","size":91,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-20T21:12:49.430Z","etag":null,"topics":["cookie-parser","cors","express","jwt","mysql2","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Washira.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-19T01:09:09.000Z","updated_at":"2024-03-22T03:06:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"38203e15-7356-46bb-afae-0371af4975a4","html_url":"https://github.com/Washira/express-guides","commit_stats":null,"previous_names":["washira/express-guides"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Washira%2Fexpress-guides","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Washira%2Fexpress-guides/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Washira%2Fexpress-guides/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Washira%2Fexpress-guides/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Washira","download_url":"https://codeload.github.com/Washira/express-guides/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243510002,"owners_count":20302294,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cookie-parser","cors","express","jwt","mysql2","nodejs"],"created_at":"2024-11-19T17:56:28.798Z","updated_at":"2025-12-29T21:37:31.704Z","avatar_url":"https://github.com/Washira.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Express Guides\n\n- [Express Guides](#express-guides)\n  - [Authentication](#authentication)\n    - [1. Set Token in `local storage` and send it to server in header](#1-set-token-in-local-storage-and-send-it-to-server-in-header)\n    - [2. Set Token in `cookie` and send it to server](#2-set-token-in-cookie-and-send-it-to-server)\n    - [3. Set Token in `session` into server](#3-set-token-in-session-into-server)\n  - [File Uploads](#file-uploads)\n    - [1. Upload file to server](#1-upload-file-to-server)\n    - [2. File Uploads with Progress Bar](#2-file-uploads-with-progress-bar)\n    - [3. Validation](#3-validation)\n      - [Validation: `size`](#validation-size)\n      - [Validation: `mimeType`](#validation-mimetype)\n    - [4. Cancel Upload](#4-cancel-upload)\n    - [5. Remove File after Cancel Upload](#5-remove-file-after-cancel-upload)\n  - [Cache Design Patterns](#cache-design-patterns)\n    - [1. Lazy loading (Cache-Aside)](#1-lazy-loading-cache-aside)\n    - [2. Write-through](#2-write-through)\n    - [3. Write-behind (Write-back)](#3-write-behind-write-back)\n  - [Elasticsearch](#elasticsearch)\n  - [Kafka Distribution System](#kafka-distribution-system)\n  - [RabbitMQ](#rabbitmq)\n\n\n## Authentication\n\ncommand\n\n```bash\nnpm init -y\nnpm i cors express mysql2 jsonwebtoken cookie-parser express-session bcrypt\n```\n\n### 1. Set Token in `local storage` and send it to server in header\n\nรับ token จาก server แล้วเก็บ token ไว้ใน local storage แล้วมีการส่งไปที่ server ผ่าน header โดยใช้ fetch api\n\nจุดสังเกต จะมีการส่ง token หลังจาก login ผ่าน `response`\n\n```js\nres.json({ message: 'Login successfully', token })\n```\n\nส่วน client จะเก็บ token ที่ได้ไว้ใน local storage \n\n```js\nlocalStorage.setItem('token', response.data.token);\n```\n\nเมื่อ request api ผ่าน Promise fn เช่น `axios` มีการส่ง token ผ่าน header ไปด้วย\n\n```js\naxios.get('http://localhost:3000/api/user', {\n  headers: {\n    Authorization: `Bearer ${token}`\n  }\n})\n```\n\n### 2. Set Token in `cookie` and send it to server\n\nในกรณีที่มีการเก็บ token ไว้ใน cookie แล้วส่งไปที่ server ผ่าน header โดยใช้ fetch api\n\nจุดสังเกต จะมีการส่ง cookie ที่ภายในมี token หลังจาก login ผ่าน `response`\n\n```js\nres.cookie('token', token, {\n  httpOnly: true,\n  secure: true,\n  sameSite: 'none'\n}).json({ message: 'Login successfully' });\n```\n\nส่วน client จะเก็บ token ที่ได้ไว้ใน cookie โดยอัตโนมัติ\n\nเมื่อ request api ผ่าน Promise fn เช่น `axios` มีการส่ง `withCredentials: true` แทนการส่ง token ผ่าน header\n\n```js\naxios.get('http://localhost:3000/api/user', {\n  withCredentials: true\n})\n```\n\n### 3. Set Token in `session` into server\n\nในกรณีนี้ server มีการเก็บ token ไว้ใน `session` ซึ่งเป็นการเก็บข้อมูลไว้ใน server โดยตรง\n\nจุดสังเกต จะมีการส่ง `session` ที่ภายในมี token หลังจาก login ผ่าน `response`\n\n```js\nreq.session.token = token;\nres.json({ message: 'Login successfully' });\n```\n\nส่วน client จะไม่ต้องทำอะไรเพิ่มเติม ตอนส่ง request api ผ่าน Promise fn เช่น `axios` ไม่ต้องส่ง token ไปด้วย\nมีการส่ง `withCredentials: true` แค่อย่างเดียว เหมือนกับกรณีที่เก็บ token ไว้ใน `cookie`\n\n```js\naxios.get('http://localhost:3000/api/user', {\n  withCredentials: true\n})\n```\n\nวิธีการนี้ จะเป็นการพึ่งพาการเก็บข้อมูลไว้ใน server โดยตรง และไม่ต้องเก็บข้อมูลไว้ที่ client และไม่ต้องส่งข้อมูลไปที่ server ทุกครั้งที่ request api\n\n## File Uploads\n\n### 1. Upload file to server\n\nเป็นการเก็บ Binary Format (ฺฺBlob) ของไฟล์ไว้ใน server โดยตรง\n\n### 2. File Uploads with Progress Bar\n\nใน `axios` มีการส่ง `onUploadProgress` ที่เป็น callback function ที่จะทำงานเมื่อมีการ upload file โดยจะส่งค่าเป็น `event` ที่มีค่า `loaded` และ `total` ที่เป็นขนาดของไฟล์ที่ถูก upload และขนาดของไฟล์ทั้งหมด\n\n```js\nconst response = await axios\n  .post('http://localhost:8000/api/upload', formData, {\n    headers: {\n      'Content-Type': 'multipart/form-data',\n    },\n    onUploadProgress: function(progressEvent) {\n      // เพิ่ม update progress กลับเข้า UI ไป\n      const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)\n      progressBar.value = percentCompleted\n      uploadPercentageDisplay.innerText = `${percentCompleted}%`\n    },\n  })\n```\n\n### 3. Validation\n\nเราสามารถ validate ไฟล์ที่ upload ได้ทั้งที่ client และ server โดยสามารถ validate ได้ทั้งขนาดของไฟล์และประเภทของไฟล์\n\n#### Validation: `size`\n\nclient สามารถทำการ validate ไฟล์ก่อนที่จะส่งไปที่ server ได้เช่น ตรวจสอบขนาดของไฟล์\n\n```js\nconst selectedFile = fileInput.files[0]\nif (selectedFile.size \u003e 1024 * 1024 * 5) {\n  return alert('Too large file, please choose a file smaller than 5MB')\n}\n```\n\nserver สามารถทำการ validate ไฟล์ที่ได้รับได้เช่น ตรวจสอบขนาดของไฟล์ และประเภทของไฟล์\n\n```js\nconst upload = multer({\n  storage,\n  limits: {\n    fileSize: 1024 * 1024 * 5, // 5MB\n  },\n})\n```\n\n#### Validation: `mimeType`\n\n`mimeType` คือประเภทของไฟล์ เช่น `image/jpeg`, `image/png`, `application/pdf` เป็นต้น ไม่เกี่ยวกันกับนามสกุลของไฟล์\n\nclient สามารถทำการ validate ไฟล์ก่อนที่จะส่งไปที่ server ได้เช่น ตรวจสอบประเภทของไฟล์\n\n```js\nconst selectedFile = fileInput.files[0]\nif (!['image/jpeg', 'image/png', 'application/pdf'].includes(selectedFile.type)) {\n  return alert('Invalid file type, please choose a valid file type')\n}\n```\n\nserver สามารถทำการ validate ไฟล์ที่ได้รับได้เช่น ตรวจสอบประเภทของไฟล์\n\n```js\nconst upload = multer({\n  storage,\n  fileFilter: (req, file, cb) =\u003e {\n    if (['image/jpeg', 'image/png', 'application/pdf'].includes(file.mimetype)) {\n      cb(null, true)\n    } else {\n      cb(new Error('Invalid file type'))\n    }\n  },\n})\n```\nเพื่อให้เอา error ที่เกิดขึ้นไปใช้งานต่อไป\nเราจึงปรับ `app.post` จากการใส่ middleware `upload.single('test')` เป็นการใข้ `upload.array('test')` ภายในแทน\n\n```js\napp.post('/api/upload', (req, res) =\u003e {\n  upload.single('test')(req, res, (err) =\u003e {\n    if (err) {\n      return res.status(400).json({ message: 'Multer error' })\n    }\n    res.json({ message: 'File uploaded successfully' })\n  })\n})\n```\n\n### 4. Cancel Upload\n\nในฝั่ง client สามารถใช้ `axios` ในการ cancel upload ได้โดยใช้ `CancelToken` และ `source` ในการสร้าง `CancelToken` และ `cancel` ในการยกเลิกการ upload\n\nใน `\u003cscript\u003e...\u003c/script\u003e` ให้สร้างตัวแปร `let currentSource = null` ไว้เพื่อเก็บ `source` ที่สร้างขึ้น\n\nจากนั้น ใน `uploadFile` ให้สร้าง `source` และเก็บไว้ใน `currentSource` และเมื่อมีการกดปุ่ม `cancel` ให้เรียก `cancelUploadBtn` ซึ่งจะเรียก `cancel` ของ `source` ที่เก็บไว้ใน `currentSource`\n\n```js\nconst source = axios.CancelToken.source() // สร้าง cancel token ขึ้นมา\ncurrentSource = source // เก็บ current source ไว้เพื่อใช้ในการ cancel ไฟล์\n```\n\nเพิ่ม `cancelToken: source.token` ไปใน `axios` ที่ส่งไปที่ server\n\n```js\nconst response = await axios.post('http://localhost:8000/api/upload', formData, {\n  headers: {\n    'Content-Type': 'multipart/form-data',\n  },\n  onUploadProgress: function(progressEvent) {\n    const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)\n    progressBar.value = percentCompleted\n    uploadPercentageDisplay.innerText = `${percentCompleted}%`\n  },\n+ cancelToken: source.token, // ส่ง cancel token ไปให้ server\n})\n```\n\nสร้าง `cancelUploadBtn` ซึ่งจะเรียก `cancel` ของ `source` ที่เก็บไว้ใน `currentSource`\n\n```js\nconst cancelUploadBtn = () =\u003e {\n  if (currentSource) {\n    currentSource.cancel('Operation canceled by the user.')\n  }\n}\n```\n\nแล้วนำ `cancelUploadBtn` ไปใช้ในปุ่ม `cancel` ที่สร้างขึ้น\n\n```html\n\u003cbutton onclick=\"cancelUploadBtn()\"\u003eCancel\u003c/button\u003e\n```\n\n### 5. Remove File after Cancel Upload\n\nเมื่อมีการ cancel upload ไฟล์ ให้ทำการลบไฟล์ที่ถูก upload ออกจาก server\n\n```js\nconst fs = require('fs')\nconst path = require('path')\n```\n\nเพิ่ม event listener ใน `filename` ของ `diskStorage` ที่จะทำการลบไฟล์ที่ upload ออกจาก server เมื่อมีการ cancel upload\n\n```js\nconst storage = multer.diskStorage({\n  destination: (req, file, cb) =\u003e {\n    cb(null, 'uploads/') // สร้าง folder ชื่อ uploads ใน root directory ของ project\n  },\n  filename: (req, file, cb) =\u003e {\n    const filename = `${Date.now()}-${file.originalname}`\n    cb(null, filename) // ใช้ชื่อเดิมของ file แต่เพิ่มเวลาที่ upload ขึ้นไปด้วย\n+    req.on('aborted', () =\u003e {\n+     // ถ้าเกิด error ในการ upload จะทำการลบ file ที่ upload ไปแล้ว\n+     const filePath = path.join('uploads', filename)\n+     fs.unlinkSync(filePath)\n+   })\n  },\n})\n```\n\n## Cache Design Patterns\n\nติดตั้ง Library ที่จำเป็น\n\n```bash\nnpm i body-parser mysql2 redis node-cron\n```\n\nเริ่มต้น Project เหมือนกับตอนที่ทำ mysql เพียงแต่เพิ่มการเชื่อมต่อกับ redis และ cron\n\n```js\nconst express = require('express')\nconst bodyParser = require('body-parser')\nconst mysql = require('mysql2')\nconst redis = require('redis')\nconst cron = require('node-cron')\n\nconst app = express()\napp.use(bodyParser.json())\nconst port = 8000\n\n/* เชื่อมต่อกับ mysql ตามปกติ (สร้าง initMySql()) */\n\nlet redisConn = null\n\nconst initRedis = async () =\u003e {\n  redisConn = redis.createClient()\n  redisConn.on('error', (err) =\u003e {\n    console.log('Redis error: ' + err)\n  })\n  await redisConn.connect()\n}\n\napp.listen(port, async () =\u003e {\n  await initMySql()\n  await initRedis()\n  console.log(`Server is running on port ${port}`)\n})\n```\n\nCache มี Sequent ทั้งหมด 3 แบบ\n\n### 1. Lazy loading (Cache-Aside)\n\nเป็นการเก็บข้อมูลไว้ใน cache โดยไม่ต้องทำการ load ข้อมูลเข้ามาทั้งหมด แต่จะทำการ load ข้อมูลเข้ามาเมื่อมีการ request ข้อมูลนั้นๆ\n\n### 2. Write-through\n\n### 3. Write-behind (Write-back)\n\n## Elasticsearch\n\n## Kafka Distribution System\n\n## RabbitMQ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwashira%2Fexpress-guides","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwashira%2Fexpress-guides","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwashira%2Fexpress-guides/lists"}