Nuxt.js Fundamental ตอนที่ 10 - การทำ Internal API และ Middleware

Published on

เขียนวันที่ : Aug 25, 2020

LastModified on

(อัพเดท : Mar 20, 2022)

Discord

ต่อมาขอเสริมเรื่องการทำ Internal API กันซักนิด ต่อยอดจาก ตอนที่ 8 - ทำระบบ Authentication

จากตอนก่อนหน้านี้ ผมใช้ API ที่อยู่อีก Server นึง ทีนี้ ถ้าตัว Nuxt.js อยากให้ API อยู่ที่เดียวกันกับตัว Nuxt Page ปกติเลย ทำได้มั้ย?

คำตอบคือทำได้ครับ

serverMiddleware

ตัว serverMiddleware ทำให้เราสามารถกำหนด API ได้เอง และส่วน API ก็สามารถเขียนได้แบบ style ของ Express.js เลยครับ

ตัวอย่างเช่นไฟล์ nuxt.config.js

export default {
  serverMiddleware: [{ path: '/api', handler: '~/api/index.js' }]
};
  • เราแค่ระบุเป็น Object path และ handler function ใน serverMiddleware
  • ส่วนไฟล์ handler ก็สามารถไว้ที่โฟลเดอร์ api (สร้างใหม่ หรือใช้ชื่ออื่นๆ ก็ได้)

ทีนี้ เราก็มาสร้างโฟลเดอร์ api ไว้ที่ level เดียวกับ pages หรือ components โดยข้างในมีไฟล์ index.js ครับ

index.js
const express = require('express');
const app = express();

app.get('/hello', (req, res) => {
  res.json({ message: 'Hello from serverMiddleware' });
});

module.exports = app;

ติดตั้ง express ด้วย npm install express ก่อนนะ ถ้าใครรันไม่ได้

ทีนี้ พอเรากำหนด แบบนี้ เราก็สามารถเข้าถึง http://localhost:3000/api/hello ก็จะแสดงผล {message: 'Hello from serverMiddleware'} ครับ

เพราะเรากำหนดใน serverMiddleware ว่า path คือ /api และในไฟล์ api/idnex.js เรากำหนด app.get('/hello') ฉะนั้น path มันก็จะต่อกันเป็น /api/hello เหมือนกับเราใช้ Express แบบนี้

const router = express.Router();
router.get('/hello', (req, res) => {});

app.use('/api', router);

สร้างไฟล์ api/auth.js

ทีนี้มาดูการใช้งานจริง ผมก็อปไฟล์ server.js จากตอนที่ 8 มาดังนี้

server.js
const express = require('express');
const cors = require('cors');

const app = express();
const router = express.Router();

app.use(cors());
app.use(express.json());

const user = {
  id: 1,
  username: 'john',
  email: 'john@doe.com',
  name: 'John Doe'
};

router.get('/me', (req, res) => {
  return res.json({
    data: {
      user
    }
  });
});

router.post('/login', (req, res) => {
  const { email, password } = req.body;

  // query db.

  if (email === 'admin@admin.com' && password === '123456') {
    return res.json({
      data: {
        user,
        token: 'THIS_IS_TOKEN'
      }
    });
  } else {
    return res.status(401).json({
      message: 'Invalid Password'
    });
  }
});

app.use('/api', router);

app.listen(12345, () => {
  console.log('Mock API start on port 12345');
});

จากนั้น ผมทำการปรับแก้ บางส่วน คือ

- app.use('/api', router);

- app.listen(12345, () => {
-   console.log('Mock API start on port 12345');
- });

+module.exports = {
+  path: '/api',
+  handler: app
+}

จากโค๊ดจะเห็นว่า ผมใช้เป็น

module.exports = {
  path: '/api',
  handler: app
};
  • เพราะ serverMiddleware กำหนดให้ Object เป็น path กับ functoin handler
  • และเอา app.use('/api', router) ออก เนื่องจากเรากำหนดใน path: '/api' แทน ก็เลยเหลือแค่ app.use(router)
  • app.listen ก็ไม่ต้องใช้แล้วครับ เพราะเราไม่ได้ start server แยก

สุดท้ายไฟล์ api/auth.js ก็จะได้แบบนี้

api/auth.js
const express = require('express');
const cors = require('cors');

const app = express();
const router = express.Router();

app.use(cors());
app.use(express.json());

const user = {
  id: 1,
  username: 'john',
  email: 'john@doe.com',
  name: 'John Doe'
};

router.get('/me', (req, res) => {
  return res.json({
    data: {
      user
    }
  });
});

router.post('/login', (req, res) => {
  const { email, password } = req.body;

  // query db.

  if (email === 'admin@admin.com' && password === '123456') {
    return res.json({
      data: {
        user,
        token: 'THIS_IS_TOKEN'
      }
    });
  } else {
    return res.status(401).json({
      message: 'Invalid Password'
    });
  }
});

app.use(router);

module.exports = {
  path: '/api',
  handler: app
};

ต่อมาที่ไฟล์ nuxt.config.js ก็ปรับแก้ serverMiddleware อีกนิด เป็นแบบนี้

export default {
  serverMiddleware: ['~/api/auth.js']
};

เราสามารถกำหนด serverMiddleware เป็น string ก็ได้ หรือ Object ก็ได้ (ถ้าเป็น string ก็ต้องเป็นไฟล์ที่เรามี Object path และ handler นั่นเอง ไม่งงเนอะ)

ทีนี้ เราก็สามารถใช้ API ได้แบบเดียวกันกับตอนที่ 8 ได้เลย โดยที่ไม่ต้อง Start server แยก ที่ port 12345 แล้ว ก็ใช้ port เดียวกันกับเว็บปกติเลยคือ 3000

ทดลองเข้า http://localhost:3000/api/me แล้วดูผลลัพธ์

Middleware

ทีนี้มาดูเรื่องของ Middlware กันบ้างครับ จริงๆ ในตอนที่ 8 เราจะเห็นว่ามีการกำหนด Middleware ในหน้า Page เช่น

<script>
  export default {
    middleware: 'auth'
  };
</script>

จริงๆแล้ว Middlware มันก็คือ function นี่แหละครับ ที่จะรันก่อน page มัน render เราสามารถสร้าง middlware แบบ global แชร์กันได้ โดยสร้างไว้ในโฟลเดอร์ middlware นั่นเอง

ตัวอย่าง middlware function (args ตัวแรกที่ถูกส่งไปคือ context) เราสามารถเข้าถึงค่า context ได้ เช่น handle ว่าเป็น server หรือ client หรือ get params เป็นต้น

เช่น ผมสร้าง middlware/logger.js ขึ้นมา

export default function (context) {
  console.log('Logging....');
}

ทีนี้ถ้าอยากใช้ middlware นี้ ก็แค่กำหนดใน page แบบนี้

<script>
  export default {
    middleware: ['logger', 'auth']
  };
</script>

ชื่อ Middleware ก็ตามชื่อไฟล์เลย

นอกจากนี้เราก็ยังสามารถสร้าง Middlware แบบ anonymous ใช้แค่ที่เดียวแบบนี้ได้

<template>
  <h1>Secret page</h1>
</template>

<script>
  export default {
    middleware({ store, redirect }) {
      // If the user is not authenticated
      if (!store.state.authenticated) {
        return redirect('/login');
      }
    }
  };
</script>

รายละเอียดเพิ่มเติมเกี่ยวกับ Middleware อ่านได้ที่นี่

Hints & Questions?

  • Internal API ถ้าเราทำการ build แบบ static และเป็น mode spa มันจะรันได้มั้ย?
  • ลองใช้ Internal API ร่วมกับ connect ดูครับ
  • serverMiddleware กับ middleware ใน Page เหมือนกันมั้ย?
Buy Me A Coffee
Discord