โปรแกรมมิ่ง

บทความต่าง ๆ เกี่ยวกับโปรแกรมมิ่ง

สร้าง API ด้วย App Script ดึงข้อมูลบน Google Sheets

บทความแนะนำการสร้าง RESTFul API โดยที่จะใช้การเก็บข้อมูลบน Google Sheets ที่ทุกคนคุณเคย แค่มี gmail account ก็สามารถสร้าง API ได้แล้ว บวกกับความรู้เรื่องการเขียน javascript พื้นฐาน เข้าใจพื้นฐานเรื่องการพัฒนาเว็บ (API) น่าจะเพียงพอในการทำ API ในบทความนี้ได้แล้ว

สร้าง Google Sheets เพื่อเป็น ฐานข้อมูล

บางท่านอาจจะไม่ทราบว่า google sheets ที่ทำหน้าที่คล้าย microsoft excel จะสามารถเขียนโปรแกรม อ่าน เขียน ข้อมูลในไฟล์ได้ ข้อดีคือไม่ว่าใครก็สามารถนำข้อมูลในไฟล์ google sheets ไปใช้งานต่อได้โดยไม่ต้องเขียน query เหมือนฐานข้อมูลแบบปกติ

  1. ให้สร้างไฟล์ google sheets ขอตั้งชื่อว่า “poolsawat.com” ชื่อไม่สำคัญ โปรแกรมจะ refer กับ sheetId ฉะนั้นชื่อจะตั้งเป็นอะไรก็ได้
  2. สร้างคอลัมน์ที่มีชื่อตามนี้ (ภาษาอังกฤษ เท่านั้น) id ,topic ,content ,author ,date ,view
  3. สำหรับข้อมูลให้ใส่ตามใจเลย

4. เริ่มเขียนโปรแกรมเลือกที่ เครื่องมือ (tools) -> โปรแกรมแก้ไขสคริป (Script editor)

5. ได้หน้า AppScript Editor เพื่อใช้เขียนโปรแกรมให้แก้ไข ตามนี้

function doGet(request) {
  // sheet id 
  var ss = SpreadsheetApp.openById("ใส่ sheet id ของคุณ");
  var sheet = ss.getActiveSheet()

  const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn()).getValues();
  // Converts data rows in json format
  const result = values.map(([a, b, c , d , e , f]) => {
    return ({ id: a, topic: b, content: c, author : d , date : e , view : f})
  })
  return ContentService.createTextOutput(JSON.stringify(result)).setMimeType(ContentService.MimeType.JSON);
}

openById : ให้ระบุ sheet id ที่ได้จาก https://docs.google.com/spreadsheets/d/187hL4DXXXXXXXXXXXXXXXXXXXXXXX-p_JOGjYxU/edit#gid=0 ซึ่งของแต่ละ sheet จะไม่ซ้ำกัน

6.กด “บันทึกโครงการ” script editor จากนั้นกด “เรียกใช้” ระบบขออนุญาตการบัญชีเจ้าของข้อมูล ก็ให้เลือกบัญชีเรา

7. ทำการเริ่มทดสอบ API

8. คัดลอกเว็บแอป URL ไปวางที่ browser ได้เลย

หลัก ๆ ขั้นตอนก็ประมาณนี้ ศึกษาเพิ่มเติมได้ที่นี้ Google App Script

Spring Boot สร้าง RESTful API พร้อม Unit Tests แบบเข้าใจง่าย และรวดเร็ว EP2

การพัฒนาแอพพลิเคชั่น ข้อแนะนำคือควรทำ Unit Tests มองการพัฒนาในระยะยาว หากต้องมีการแก้ไข issue เล็กน้อย แต่ไม่แน่ใจว่าจะเกิด defect กับระบบเดิมหรือไม่ การมี unit tests ช่วยประหยัดเวลาการ regression tests ได้เยอะมาก และคุณจะเห็นประโยชน์ของการทำ Unit tests อย่างแน่นอน

รู้จัก @RestController annotation สร้าง routes RESTful API

annotation @RestController implement มาจาก @Controller ของ spring web เพื่อใช้งานสำหรับการสร้าง RESTful API (ไม่ใช่ static view) เพิ่มความสะดวกในการพัฒนา RESTful API ให้ง่ายยิ่งขึ้น

ตัวอย่างการสร้าง Route (text/plain)

package com.poolsawat.starter.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
	
	@RequestMapping("/")
	public String index() {
		return "Greetings from Spring Boot!";
	}
	
}

ทดสอบ http://localhost:8080/

Greetings from Spring Boot!

เพิ่ม Route (application/json)

... 
	@RequestMapping("/domain")
	public String[] json() {		
		return new String[] {"www",".","poolsawat",".","com"};
	}
...

restart server จากนั้น ทดสอบ http://localhost:8080/domain

[
  "www",
  ".",
  "poolsawat",
  ".",
  "com"
]

เพิ่ม Test Dependencies ใน POM

ก่อนที่จะเริ่มเขียน Unit Tests จำเป็นต้องเพิ่ม “spring-boot-starter-test” เข้าไปที่ไฟล์ POM (ไฟล์ pom.xml) มาเพิ่มกันเลย

...
<dependencies>
	...
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	...
</dependencies>
...

สร้าง src/test/java/com/poolsawat/starter/controller/HelloControllerTest.java

package com.poolsawat.starter.controller;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {
	@Autowired
	private MockMvc mvc;

	@Test
	public void getHello() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().string(equalTo("Greetings from Spring Boot!")));
	}
	@Test
	public void getDomain() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/domain")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().json("[\"www\",\".\",\"poolsawat\",\".\",\"com\"]"));
	}
}

อธิบายการทำงาน

  • @SpringBootTest inject annotation Test เพื่อเริมการ Test
  • @AutoConfigureMockMvc inject annotation AutoConfiMockMVC (MockMVC มีหน้าที่สำหรับทำ Unit Tests มี feature mockup data)
  • @Autowired เป็นการ auto inject MockMVC bean ก่อนการจะเรียกใช้งานใน Testcase
  • @Test ระบุที method บอกให้รู้ว่า รันเคส method นี้
  • mvn.perform mockup route ที่ต้องการทดสอบ ระบุ MockMvcRequestBuilders.get(“/”) หมายถึง (“localhost:8080/”) ตรวจสอบ content-type คือต้องเป็น “MediaType.APPLICATION_JSON” กำหนดให้เป็น application/json เท่านั้น
  • andExpect
    • .andExpect(status().isOk()) expect htto status code OK (status code 200) หากไม่ใช่จะ fail ทันที
    • .andExpect(content().string(equalTo(“Greetings from Spring Boot!”))) expect content body ต้องเป็นคำว่า “Greetings from Spring Boot!” หากไม่ใช่จะ fail ทันที

จากนั้นสร้าง maven test build

apply -> run

ตรวจสอบที่ console panel

[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.poolsawat.starter.controller.HelloControllerTest
...
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.606 s - in com.poolsawat.starter.controller.HelloControllerTest
2020-10-15 10:57:54.262  INFO 5864 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.231 s
[INFO] Finished at: 2020-10-15T10:57:54+07:00
[INFO] Final Memory: 18M/220M
[INFO] ------------------------------------------------------------------------

ในทุกครั้งที่มีการแก้ไข java code จำเป็นต้องมีการ restart web server spring boot มี hot reload (การ auto restart web server เมื่อมี code change)

เพิ่ม “spring-boot-devtools”

...
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<optional>true</optional>
</dependency>
...

จากนั้นทำการ restart อีกครั้ง ต่อไปเวลาแก้ไข code ก็จะไม่ต้อง restart เองแล้ว

สรุปท้ายบทความ

การพัฒนาแอพพลิเคชั่นที่มี Test ถือว่าเป็นแอพพลิเคชั่นที่ดี ข้อดีของการทำ Test มีเยอะมากช่วยลดเวลาการ Regression Test ได้เยอะ ถ้าแอพพลิเคชั่นของคุณยังไม่มี test เริ่มทำได้แล้ว เพราะถ้าไม่ทำคุณจะคุยกับเขา (ทีมพัฒนา แอพพลิเคชั่นที่มี test) ไม่รู้เรื่อง บทความต่อไปจะเป็นเรื่องเกี่ยวกับอะไร คอยติดตามกันนะครับ

Spring Boot ทำความรู้จัก เริ่มติดตั้งและเริ่มใช้งานเบื้องต้น EP1

Spring boot คืออะไร

spring boot เป็น framework package ที่ต่อยอดมาจาก spring framework ช่วยให้เราสามารสร้างแอพพลิเคชั่น (application) แบบ stand-alone แบบรวดเร็ว และง่ายดาย

บ่อยครั้งที่ต้องมานั่งปวดหัวกับเรื่องการ configuration project ในขั้นตอนการ setup เริ่มต้นในการสร้างแอพพลิเคชั่น ปัญหาเหล่านี้จะถูกทำให้เสร็จเรียบร้อยแล้ว เมื่อมาใช้งาน spring boot

Features ต่าง ๆ ที่มีมาใน Spring boot

  • การสร้างแอพพลิเคชั่นแบบ stand-alone
  • embed Tomcat ,Jetty web server ยอดนิยมมาให้เรียบร้อยแล้ว หรือแม้แต่ Underflow ก็มีมาให้แล้ว
  • มีการกำหนด dependencies starter (POM) สำหรับพัฒนาไม่ต้องเพิ่มหลาย dependencies เหมือน spring framework แบบแต่ก่อน
  • มี auto configuration มาให้พร้อม ไม่ต้องยุ่งวุ้นวายกับการ config project เหมือนแต่ก่อน (ดีม๊วก ๆ )
  • ให้ feature monitoring ,metrics, health checks มาพร้อมแล้วเรียกใช้งานได้เลย
  • ไม่มีการสร้าง XML configuration file อีกต่อไปเพราะทุกอย่างจะ code บน java (kotlin) ทั้งหมดเลย

จะเริ่มเขียน Spring boot ต้องเตรียมตัวอย่างไรบ้าง

สำหรับมือใหม่ หลาย ๆ คนที่กำลังจะเรียนรู้ spring framework สั่งที่ต้องทราบก่อนที่จะเริ่มพัฒนาแอพพลิเคชั่นด้วย Java Framework อย่าง Spring Boot ต้องรู้เกี่ยวกับอะไร จะอธิบายย่อย ๆ ตามนี้

  • IDE (Integrated Development Environment) เครื่องมือสำหรับพัฒนาแอพพลิเคชั่น ที่ดัง ๆ สำหรับพัฒนา Java ก็พวก Eclipse ,IntelliJ IDEA เป็นต้น
  • Maven (Project Management Tools) สำหรับ Java Developer ทำงานได้ง่ายยิ่งขึ้น การที่จะต้องคอยหา JAR หรือบางครั้งเราจะเรียกว่า lib (dependencies) ที่มีมากมายซะเหลือเกิน อีกทั้งยังต้องมานั่งตรวจสอบเรื่องเวอชั่นของ JAR อีกละก็ ไม่สนุกแน่ ๆ สำหรับ Developer Java ปัญหาเหล่านี้จะถูกแก้ไขด้วย Maven อีกทั้งยังมีความสามารถในการทำกระบวนการ Build ,Compile , Test ,Deploy ,Documentation ได้อีกมากมาย อีกทั้งยังมี plugins ให้เรียกใช้งานอีกเพียบเลย ถือว่าดีม๊วก ๆ
  • Spring IO หน่วยงานพัฒนา Java Framework ที่โด่งดัง และยังคงได้รับความนิยมจนถึงยุคปัจจุบันด้วยความที่ไม่หยุดพัฒนา จึงทำให้ framework ได้รับความนิยมอยู่จนถึงปัจจุบัน

เริ่มติดตั้ง และสร้าง Hello World กัน

การ initializr Spring Boot Project มีด้วยกันหลายวิธี ผมขอยกตัวอย่างวิธีนี้

  1. เข้าไป generate spring boot project ที่ https://start.spring.io/
    1. Project ให้เลือก Maven Project (Gradle Project) เป็นเครื่องมืออีกแบบ ฝั่ง mobile dev ชอบใช้กัน เพราะมีมาใน android project)
    2. Language ให้เลือก Java (แล้วแต่ความชำนาญ ของผู้พัฒนาจะเลือกเป็น Kotlin หรือ Groovy ก็ได้)
    3. Spring Boot ให้เลือก version 2.3.4 เป็นเวอร์ชั่น ที่ stable แล้ว
    4. Project Metadata
      1. Group ใส่เป็น com.poolsawat (กำหนดเป็น domain ขององค์กร)
      2. Artifact ใส่เป็น Poolsawat (ชื่อสิ่งประดิษฐ์กำหนดเป็นชื่อของแอพพลิเคชั่น )
      3. Name ใส่เป็น Poolsawat (ชื่อแอพพลิเคชั่น)
      4. Description ใส่ Demo project for Spring Boot หรือจะใส่อธิบาย destination ของแอพพลิเคชั่น สั้น ๆ
      5. Package name ใส่ com.poolsawat.starter ใส่เป็น java package สำหรับเก็บ source code จะกำหนดภายใต้ src/com/poolsawat/starter/**
      6. Packaging เลือก Jar เพราะจะเลือกใช้ stand-lone application ที่รันด้วย embed Tomcat web server
      7. Java เลือก 8 (จะเลือก 10 , 15 แล้วแต่ JDK ในเครื่องที่ติดตั้งไว้)
      8. Dependencies เลือก “web”
      9. กด “GENERATE” downloadลงเครื่อง จะได้ไฟล์ .zip มา รอทำในขั้นถัดไป

2. เปิด IDE ตัวอย่างจะใช้ Eclipse IDE
        1. Unzip Poolsawat.zip ย้ายไฟล์ทั้งหมดไปยัง workspace ของ Eclipse ที่สร้างไว้
        2. หน้า Eclipse เลือก File -> Import… -> Maven -> Poolsawat

        3. จะมี Poolsawat เข้ามาที่ Project Explorer ทางซ้ายมือของ Eclipse IDE

3. แก้ไข Code ไฟล์ PoolsawatApplication.java ตามนี้

package com.poolsawat.starter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class PoolsawatApplication {

	public static void main(String[] args) {
		SpringApplication.run(PoolsawatApplication.class, args);
	}

	@GetMapping("/hello")
	public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
		return String.format("Hello %s!", name);
	}

}

4. สร้าง Maven build เลือกเมนู Run -> Run Configurations… -> Maven Build -> New Launch configuration -> ใส่ค่าตามภาพนี้

Apply -> Run

5. รอ… maven download dependencies สักครู่…..

...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.4.RELEASE)

2020-10-15 00:06:06.708  INFO 4832 --- [           main] c.p.starter.PoolsawatApplication         : Starting PoolsawatApplication on PoolPC-PC with PID 4832 (D:\workspace-blog\Poolsawat\target\classes started by PoolPC in D:\workspace-blog\Poolsawat)
2020-10-15 00:06:06.711  INFO 4832 --- [           main] c.p.starter.PoolsawatApplication         : No active profile set, falling back to default profiles: default
2020-10-15 00:06:07.773  INFO 4832 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-10-15 00:06:07.781  INFO 4832 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-10-15 00:06:07.782  INFO 4832 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.38]
2020-10-15 00:06:07.848  INFO 4832 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-10-15 00:06:07.848  INFO 4832 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1078 ms
2020-10-15 00:06:08.018  INFO 4832 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-10-15 00:06:08.174  INFO 4832 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-10-15 00:06:08.191  INFO 4832 --- [           main] c.p.starter.PoolsawatApplication         : Started PoolsawatApplication in 1.823 seconds (JVM running for 2.231)

6. ทดสอบเข้าลิ้ง http://localhost:8080/hello

สรุปท้ายบทความ

การพัฒนาเว็บด้วย Spring boot นั้นทำได้ไม่ยากเลย ค่อนข้างง่ายกว่าวิธีการเดิมของ Spring Framework ที่ผ่าน ๆ มา ไม่ต้อง configuration ให้ยุ่งยาก ไม่ต้องลำคาญกับการต้องมา control version ของ dependencies ที่มากมายซะเหลือเกิน เลยทำให้ผมชอบที่จะใช้งาน spring boot กับทุก ๆ project ของผมเลยครับ บทความหน้าจะเป็นเรื่องอะไร คอยติดตามกันนะครับ

Laravel connect MySQL Database ทำ RESTful API CRUD [เพิ่ม(create) ,อ่าน(read) , แก้ไข(update), ลบ(delete)] EP6

Laravel Series เดินทางมาถึง EP6 ซึ่งถ้าใครยังไม่ได้อ่านบทความ EP 1 – 5 สามารถเข้าได้ที่ลิ้งใต้ล่างนี้

  1. Laravel เริ่มติดตั้งและสร้าง route ง่าย ๆ EP1
  2. Laravel แนะนำโครงสร้างภายในโปรเจค อธิบายแต่ละส่วนการทำงาน EP2
  3. Laravel ใช้งาน views blade template engine EP3
  4. Laravel รู้จัก Routes และ การรับค่าจาก HttpRequest EP4
  5. Laravel สร้าง RESTful API ง่าย ๆ ด้วย Resource Routes Controller EP5

บทความนี้จะมาทำให้ application ของเราต่อกับฐานข้อมูล (database) ของ MySQL และเมื่อต่อได้แล้วจะมาใช้งาน migrate และมาทำระบบ CRUD ของข้อมูล Photos กัน

สร้าง Schema และ Migrate Table ด้วย artisan migrate

  1. สร้าง schema ด้วยคำสั่ง
CREATE SCHEMA `db_poolsawat` DEFAULT CHARACTER SET utf8 ;

2. แก้ไขไฟล์ .env

...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db_poolsawat
DB_USERNAME=root
[email protected]
...

3. ทดสอบการต่อ connection ทำตามขั้นตอนข้างล่าง ถ้าขึ้นแบบนี้แสดงว่าต่อสำเร็จ

$ php artisan tink
// Psy Shell v0.10.4 (PHP 7.3.7 — cli) by Justin Hileman

$ DB::connection()->getPdo();
/* 
PDO {#4182
     inTransaction: false,
     attributes: {
       CASE: NATURAL,
       ERRMODE: EXCEPTION,
       AUTOCOMMIT: 1,
       PERSISTENT: false,
       DRIVER_NAME: "mysql",
       SERVER_INFO: "Uptime: 433  Threads: 4  Questions: 152  Slow queries: 0  Opens: 209  Flush tables: 3  Open tables: 130  Queries per second avg: 0.351",
       ORACLE_NULLS: NATURAL,
       CLIENT_VERSION: "mysqlnd 5.0.12-dev - 20150407 - $Id: 7cc7cc96e675f6d72e5cf0f267f48e167c2abb23 $",
       SERVER_VERSION: "8.0.21",
       STATEMENT_CLASS: [
         "PDOStatement",
       ],
       EMULATE_PREPARES: 0,
       CONNECTION_STATUS: "127.0.0.1 via TCP/IP",
       DEFAULT_FETCH_MODE: BOTH,
     },
   }
*/

4. generate migrations file ด้วยคำสั่งเหล่านี้

php artisan make:migration create_photos_table

4.1 ตรวจสอบไฟล์ที่ถูกสร้างขึ้นมาที่ database/migrations/*_create_photos_table.php (* คือวันที่ generate file) จากนั้นเปิดไฟล์ขึ้นมาแก้ไข ตามนี้

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePhotosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('photos', function (Blueprint $table) {
            $table->id();
            $table->string('photo_name');
            $table->integer('photo_size');
            $table->string('photo_url');
            $table->enum('photo_status', ['active', 'inactive']);
            $table->dateTime('photo_date', 0);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('photos');
    }
}

ในการกำหนด datatype ของ column ตารางมีรูปแบบที่หลากหลาย มี function รองรับอยู่แล้ว สำหรับการกำหนดสร้าง สามารถดูเพิ่มเติมได้จากตารางนี้

Available Column Types

The schema builder contains a variety of column types that you may specify when building your tables:

CommandDescription
$table->id();Alias of $table->bigIncrements('id').
$table->foreignId('user_id');Alias of $table->unsignedBigInteger('user_id').
$table->bigIncrements('id');Auto-incrementing UNSIGNED BIGINT (primary key) equivalent column.
$table->bigInteger('votes');BIGINT equivalent column.
$table->binary('data');BLOB equivalent column.
$table->boolean('confirmed');BOOLEAN equivalent column.
$table->char('name', 100);CHAR equivalent column with a length.
$table->date('created_at');DATE equivalent column.
$table->dateTime('created_at', 0);DATETIME equivalent column with precision (total digits).
$table->dateTimeTz('created_at', 0);DATETIME (with timezone) equivalent column with precision (total digits).
$table->decimal('amount', 8, 2);DECIMAL equivalent column with precision (total digits) and scale (decimal digits).
$table->double('amount', 8, 2);DOUBLE equivalent column with precision (total digits) and scale (decimal digits).
$table->enum('level', ['easy', 'hard']);ENUM equivalent column.
$table->float('amount', 8, 2);FLOAT equivalent column with a precision (total digits) and scale (decimal digits).
$table->geometry('positions');GEOMETRY equivalent column.
$table->geometryCollection('positions');GEOMETRYCOLLECTION equivalent column.
$table->increments('id');Auto-incrementing UNSIGNED INTEGER (primary key) equivalent column.
$table->integer('votes');INTEGER equivalent column.
$table->ipAddress('visitor');IP address equivalent column.
$table->json('options');JSON equivalent column.
$table->jsonb('options');JSONB equivalent column.
$table->lineString('positions');LINESTRING equivalent column.
$table->longText('description');LONGTEXT equivalent column.
$table->macAddress('device');MAC address equivalent column.
$table->mediumIncrements('id');Auto-incrementing UNSIGNED MEDIUMINT (primary key) equivalent column.
$table->mediumInteger('votes');MEDIUMINT equivalent column.
$table->mediumText('description');MEDIUMTEXT equivalent column.
$table->morphs('taggable');Adds taggable_id UNSIGNED BIGINT and taggable_type VARCHAR equivalent columns.
$table->uuidMorphs('taggable');Adds taggable_id CHAR(36) and taggable_type VARCHAR(255) UUID equivalent columns.
$table->multiLineString('positions');MULTILINESTRING equivalent column.
$table->multiPoint('positions');MULTIPOINT equivalent column.
$table->multiPolygon('positions');MULTIPOLYGON equivalent column.
$table->nullableMorphs('taggable');Adds nullable versions of morphs() columns.
$table->nullableUuidMorphs('taggable');Adds nullable versions of uuidMorphs() columns.
$table->nullableTimestamps(0);Alias of timestamps() method.
$table->point('position');POINT equivalent column.
$table->polygon('positions');POLYGON equivalent column.
$table->rememberToken();Adds a nullable remember_token VARCHAR(100) equivalent column.
$table->set('flavors', ['strawberry', 'vanilla']);SET equivalent column.
$table->smallIncrements('id');Auto-incrementing UNSIGNED SMALLINT (primary key) equivalent column.
$table->smallInteger('votes');SMALLINT equivalent column.
$table->softDeletes('deleted_at', 0);Adds a nullable deleted_at TIMESTAMP equivalent column for soft deletes with precision (total digits).
$table->softDeletesTz('deleted_at', 0);Adds a nullable deleted_at TIMESTAMP (with timezone) equivalent column for soft deletes with precision (total digits).
$table->string('name', 100);VARCHAR equivalent column with a length.
$table->text('description');TEXT equivalent column.
$table->time('sunrise', 0);TIME equivalent column with precision (total digits).
$table->timeTz('sunrise', 0);TIME (with timezone) equivalent column with precision (total digits).
$table->timestamp('added_on', 0);TIMESTAMP equivalent column with precision (total digits).
$table->timestampTz('added_on', 0);TIMESTAMP (with timezone) equivalent column with precision (total digits).
$table->timestamps(0);Adds nullable created_at and updated_at TIMESTAMP equivalent columns with precision (total digits).
$table->timestampsTz(0);Adds nullable created_at and updated_at TIMESTAMP (with timezone) equivalent columns with precision (total digits).
$table->tinyIncrements('id');Auto-incrementing UNSIGNED TINYINT (primary key) equivalent column.
$table->tinyInteger('votes');TINYINT equivalent column.
$table->unsignedBigInteger('votes');UNSIGNED BIGINT equivalent column.
$table->unsignedDecimal('amount', 8, 2);UNSIGNED DECIMAL equivalent column with a precision (total digits) and scale (decimal digits).
$table->unsignedInteger('votes');UNSIGNED INTEGER equivalent column.
$table->unsignedMediumInteger('votes');UNSIGNED MEDIUMINT equivalent column.
$table->unsignedSmallInteger('votes');UNSIGNED SMALLINT equivalent column.
$table->unsignedTinyInteger('votes');UNSIGNED TINYINT equivalent column.
$table->uuid('id');UUID equivalent column.
$table->year('birth_year');YEAR equivalent column.
https://laravel.com/docs/8.x/migrations

4.2 หลังจากที่แก้ไขไฟล์ *_create_photos_table.php เป็นที่เรียบร้อยแล้ว ให้ รันคำสั่งต่อไปนี้เพิ่มเพิ่ม migrate table (artisan จะทำการไป create table ให้ที่ database )

php artisan migrate

เปรียบเหมือนว่าเรากำลังใช้คำสั่งชุดนี้เพื่อสร้างตาราง

CREATE TABLE `photos` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `photo_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `photo_size` int NOT NULL,
  `photo_url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `photo_status` enum('active','inactive') COLLATE utf8mb4_unicode_ci NOT NULL,
  `photo_date` datetime NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

5. สร้าง Model class ด้วย artisan cli

php artisan make:model Photo

ตรวจสอบไฟล์ที่ app/Models/Photo.php

5.1 แก้ไขไฟล์ app/Models/Photo.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Photo extends Model
{
  use HasFactory;
  protected $fillable = [
    'created_at', 'id', 'photo_date', 'photo_name', 'photo_size',
    'photo_status', 'photo_url', 'updated_at'
  ];
}

6. สร้าง Controller class ด้วย artisan cli

php artisan make:controller PhotoController --resource

ตรวจสอบไฟล์ที่ app/Http/Controller/PhotoController.php

6.1 แก้ไขไฟล์ app/Http/Controller/PhotoController.php ตามข้างล่างนี้ ส่วนนี้จะได้มี function RESTful API template ที่ได้จากการ artisan cli แล้ว บทความ “Laravel สร้าง RESTful API ง่าย ๆ ด้วย Resource Routes Controller EP5” ได้อธิบายไว้บ้างแล้ว บทความนี้จะขอข้ามส่วนนี้ไป

<?php

namespace App\Http\Controllers;

use App\Models\Models\Photo as ModelsPhoto;
use App\Models\Photo;
use Carbon\Carbon;
use Illuminate\Http\Request;

class PhotoController extends Controller
{
  /**
   * Display a listing of the resource.
   *
   * @return \Illuminate\Http\Response
   */
  public function index()
  {
    return response()->json(['name' => 'index', 'photos' => Photo::all()]);
  }

  /**
   * Show the form for creating a new resource.
   *
   * @return \Illuminate\Http\Response
   */
  public function create()
  {
    $photo = new Photo();
    $photo->photo_name = 'laravel';
    $photo->photo_size = 1024;
    $photo->photo_url = 'https://laravel.com/img/logotype.min.svg';
    $photo->photo_status = 'active';
    $photo->photo_date = Carbon::now();
    return response()->json(['name' => 'create', 'status' => $photo->save()]);
  }

  /**
   * Store a newly created resource in storage.
   *
   * @param  \Illuminate\Http\Request  $request
   * @return \Illuminate\Http\Response
   */
  public function store(Request $request)
  {
    $photo = new Photo($request->all());
    $photo->photo_date = Carbon::now();
    return response()->json(['name' => 'store', 'payload' => $request->all(), 'status' => $photo->save()]);
  }

  /**
   * Display the specified resource.
   *
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function show($id)
  {
    $photo = Photo::find($id);
    return response()->json(['name' => 'show', 'id' => $id, 'photo' => $photo]);
  }

  /**
   * Show the form for editing the specified resource.
   *
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function edit($id)
  {
    $photo = Photo::find($id);
    return response()->json(['name' => 'edit', 'id' => $id, 'photo' => $photo]);
  }

  /**
   * Update the specified resource in storage.
   *
   * @param  \Illuminate\Http\Request  $request
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function update(Request $request, $id)
  {
    $photo = Photo::find($id);
    $photo->photo_name = $request->photo_name;
    $photo->photo_size = $request->photo_size;
    $photo->photo_url = $request->photo_url;
    $photo->photo_status = $request->photo_status;
    $photo->photo_date = Carbon::now();
    return response()->json(['name' => 'update', 'status' => $photo->save(),  'payload' => $request->all(), 'id' => $id]);
  }

  /**
   * Remove the specified resource from storage.
   *
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function destroy($id)
  {
    $photo = Photo::find($id);
    return response()->json(['name' => 'destroy', 'status' => $photo->delete(), 'id' => $id]);
  }
}

7. แก้ไขไฟล์ routes/api.php เพิ่มโค๊ด 1 บรรทัด

...
Route::resource('photos', 'App\Http\Controllers\PhotoController');
...

8. ทดสอบ RESTful API

ก่อนที่จะทดสอบติดตั้ง npm i -g json เพื่อ pritty json response เพื่อความสวยงาม

$ curl localhost:8000/api/photos | json

 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    28    0    28    0     0     65      0 --:--:-- --:--:-- --:--:--    65
{
  "name": "index",
  "photos": []
}
/* fetch photos all */
$ curl localhost:8000/api/photos/create | json

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    31    0    31    0     0     73      0 --:--:-- --:--:-- --:--:--    73
{
  "name": "create",
  "status": true
}
/* create photo */
$ curl -X POST -H "Content-Type: application/json" -d "{\"photo_name\":\"laravel\",\"photo_size\":1024,\"photo_url\":\"https://laravel.com/img/logotype.min.svg\",\"photo_status\":\"active\",\"photo_date\":\"2020-10-06 10:24:01\"}" http://127.0.0.1:8000/api/photos | json

 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   357    0   201  100   156   1116    866 --:--:-- --:--:-- --:--:--  1994
{
  "name": "store",
  "payload": {
    "photo_name": "laravel",
    "photo_size": 1024,
    "photo_url": "https://laravel.com/img/logotype.min.svg",
    "photo_status": "active",
    "photo_date": "2020-10-06 10:24:01"
  },
  "status": true
}
/* create photo with payload */
$ curl localhost:8000/api/photos/1 | json

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   286    0   286    0     0    722      0 --:--:-- --:--:-- --:--:--   722
{
  "name": "show",
  "id": "1",
  "photo": {
    "id": 1,
    "photo_name": "laravel",
    "photo_size": 1024,
    "photo_url": "https://laravel.com/img/logotype.min.svg",
    "photo_status": "active",
    "photo_date": "2020-10-06 10:32:24",
    "created_at": "2020-10-06T10:32:24.000000Z",
    "updated_at": "2020-10-06T10:32:24.000000Z"
  }
}
/* fetch photo by id */
$ curl localhost:8000/api/photos/1/edit | json

 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   286    0   286    0     0    709      0 --:--:-- --:--:-- --:--:--   709
{
  "name": "edit",
  "id": "1",
  "photo": {
    "id": 1,
    "photo_name": "laravel",
    "photo_size": 1024,
    "photo_url": "https://laravel.com/img/logotype.min.svg",
    "photo_status": "active",
    "photo_date": "2020-10-06 10:32:24",
    "created_at": "2020-10-06T10:32:24.000000Z",
    "updated_at": "2020-10-06T10:32:24.000000Z"
  }
}
/* fetch photo by id for edit */
$ curl -X PATCH -H "Content-Type: application/json" -d "{\"id\":1,\"photo_name\":\"laravel patch\",\"photo_size\":2048,\"photo_url\":\"https://laravel.com/img/logotype.min.svg\",\"photo_status\":\"active\",\"photo_date\":\"2020-10-06 10:24:01\"}" http://127.0.0.1:8000/api/photos/1 | json

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   393    0   224  100   169   1108    836 --:--:-- --:--:-- --:--:--  1945
{
  "name": "update",
  "status": true,
  "payload": {
    "id": 1,
    "photo_name": "laravel patch",
    "photo_size": 2048,
    "photo_url": "https://laravel.com/img/logotype.min.svg",
    "photo_status": "active",
    "photo_date": "2020-10-06 10:24:01"
  },
  "id": "1"
}
/* patch photo with payload */

$ curl localhost:8000/api/photos/1 | json
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   292    0   292    0     0    707      0 --:--:-- --:--:-- --:--:--   705
{
  "name": "show",
  "id": "1",
  "photo": {
    "id": 1,
    "photo_name": "laravel patch",
    "photo_size": 2048,
    "photo_url": "https://laravel.com/img/logotype.min.svg",
    "photo_status": "active",
    "photo_date": "2020-10-06 10:36:22",
    "created_at": "2020-10-06T10:32:24.000000Z",
    "updated_at": "2020-10-06T10:36:22.000000Z"
  }
}
curl -X DELETE http://127.0.0.1:8000/api/photos/1 | json

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    41    0    41    0     0    221      0 --:--:-- --:--:-- --:--:--   221
{
  "name": "destroy",
  "status": true,
  "id": "1"
}
/* delete photo by id */

สรุปท้ายบทความ

บทความนี้ต่อยอดความสามารถของบทความ “Laravel สร้าง RESTful API ง่าย ๆ ด้วย Resource Routes Controller EP5” ที่ RESTful template แต่บทความนี้จะสอนเพิ่มเติมการต่อ MySQL เพื่อ connect database ใช้ข้อมูลภายใน เพิ่มเติมด้วยความสามารถของ migration ที่จะ create table schema ให้เองตาม code CreatePhotosTable ที่ได้ทำการสร้างไว้จาก code PHP

บทความหน้าจะมาพูดถึงเรื่องอะไรค่อยติดตามกันนะครับ ขอบคุณที่ติดตามครับ

Laravel สร้าง RESTful API ง่าย ๆ ด้วย Resource Routes Controller EP5

รูปแบบลักษณะงานเว็บไซต์ในปัจจุบัน เกิน 80 – 90 % ต้องมีการทำระบบ RESTful API เพื่อให้บริการข้อมูลกับ Application platform รูปแบบต่าง ๆ ไม่ว่าจะเป็น mobile และ web เพราะเป็นรูปแบบการพัฒนาที่ได้รับความนิยมมากในปัจจุบัน กล่าวคือการเชื่อมโยง application ด้วยข้อมูลจากแหล่งเดียวกัน นั้นเอง

การพัฒนา RESTful API Service สามารถทำได้กับหลาย ๆ ภาษาโปรแกรมมิ่งทั่วไป (ถ้าภาษาโปรแกรมมิ่งมี feature http ต้องสามารถทำ RESTful API ได้อย่างแน่นอน) แต่สิ่งที่จะทำให้การพัฒนา RESTful นั้นง่ายและสะดวกยิ่งขึ้นก็คือเครื่องมือ หรือ cli (command line user interface ) สำหรับช่วยทำให้การเขียนโปรแกรมของเราง่าย และสะดวกมากยิ่งขึ้นไปอีก

laravel มี artisan cli (symfony class console) ช่วยทำให้งาน develop สะดวกและง่ายยิ่งขึ้น บทความนี้จะพามาทำความรู้จัก cli นี้กัน

สร้าง Resource Controller RESTful API แรกกัน

laravel มี feature Resource Controllers ที่จะช่วยสร้าง RESTful function template controller ขึ้นมาให้ เราเพียงแค่ implement function ตาม RESTful action เท่านั้นก็สามารถเรียกใช้งานได้เลย โดยมีวิธีการเรียกใช้งานตามตัวอย่าง ข้างล่างนี้

$ php artisan make:controller PhotoController --resource

laravel สร้าง generate /app/Http/Controllers/PhotoController.php ภายในไฟล์จะมีการสร้าง function RESTful template มาให้

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PhotoController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

มาทำความรู้จักการทำงานแต่ละ function มามีการทำงานอย่างไร ตามการอธิบายจากตารางนี้

Actions Handled By Resource Controller

VerbURIActionRoute Name
GET/photosindexphotos.index
GET/photos/createcreatephotos.create
POST/photosstorephotos.store
GET/photos/{photo}showphotos.show
GET/photos/{photo}/editeditphotos.edit
PUT/PATCH/photos/{photo}updatephotos.update
DELETE/photos/{photo}destroyphotos.destroy

เพิ่ม routes mapping ที่ไฟล์ /routes/api.php

Route::resource('photos', 'App\Http\Controllers\PhotoController');

เพิ่ม route:resource เป็น static function สำหรับการทำ RESTful routes เพิ่มเพียงบรรทัดเดียวก็สามารถเรียก http methods ต่าง ๆ เหล่านี้ GET ,POST ,PUT ,DELETE ,PATCH ได้เลย ง่ายมาก ๆ

implement PhotoController ตาม function การทำงานเพื่อใช้ตรวจสอบการ call จาก REST client

ผมจะเพิ่ม function response()->json(); เพื่อทำการ return http response คืนค่ากลับไปในรูปแบบ application/json format

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PhotoController extends Controller
{
  /**
   * Display a listing of the resource.
   *
   * @return \Illuminate\Http\Response
   */
  public function index()
  {
    return response()->json(['name' => 'index']);
  }

  /**
   * Show the form for creating a new resource.
   *
   * @return \Illuminate\Http\Response
   */
  public function create()
  {
    return response()->json(['name' => 'create']);
  }

  /**
   * Store a newly created resource in storage.
   *
   * @param  \Illuminate\Http\Request  $request
   * @return \Illuminate\Http\Response
   */
  public function store(Request $request)
  {
    return response()->json(['name' => 'store', 'payload' => $request->all()]);
  }

  /**
   * Display the specified resource.
   *
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function show($id)
  {
    return response()->json(['name' => 'show', 'id' => $id]);
  }

  /**
   * Show the form for editing the specified resource.
   *
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function edit($id)
  {
    return response()->json(['name' => 'edit', 'id' => $id]);
  }

  /**
   * Update the specified resource in storage.
   *
   * @param  \Illuminate\Http\Request  $request
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function update(Request $request, $id)
  {
    return response()->json(['name' => 'update', 'payload' => $request->all(), 'id' => $id]);
  }

  /**
   * Remove the specified resource from storage.
   *
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function destroy($id)
  {
    return response()->json(['name' => 'destroy', 'id' => $id]);
  }
}

หลังจากปรับแก้ PhotoController.php เรียบร้อย ทำการ รันคำสั่ง

$ php artisan serve
Starting Laravel development server: http://127.0.0.1:8000

ทดสอบ RESTful API

curl http://127.0.0.1:8000/api/photos

{“name”:”index”}

curl http://127.0.0.1:8000/api/photos/create

{“name”:”create”}

curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"poolsawat.com\"}" http://127.0.0.1:8000/api/photos

{“name”:”store”,”payload”:{“name”:”poolsawat.com”}}

curl http://127.0.0.1:8000/api/photos/9999

{“name”:”show”,”id”:”9999″}

curl http://127.0.0.1:8000/api/photos/9999/edit

{“name”:”edit”,”id”:”9999″}

curl -X PUT -H "Content-Type: application/json" -d "{\"name\":\"poolsawat.com\"}" http://127.0.0.1:8000/api/photos/99999

{“name”:”update”,”payload”:{“name”:”poolsawat.com”},”id”:”99999″}

curl -X DELETE http://127.0.0.1:8000/api/photos/9999

{“name”:”destroy”,”id”:”9999″}

สรุปท้ายบทความ

การทำ RESTful API ด้วย Laravel จากที่ได้ใช้งานมาได้สักระยะ จากประสบการณ์ค่อนข้างชอบ เพราะด้วยตัวภาษาโปรแกรมมิ่ง เวลาจะ compile ไม่จำเป็นต้อง restart server เพราะเป็นภาษาคริป เลยถือเป็นข้อดีอย่างนึงสำหรับทำ prototype api ง่าย ๆ เพื่อนำเสนอลูกค้าหรือใช้งานกันเองภายในบริษัท ต้องการศึกษาเพิ่มเติมเกี่ยวกับการทำ Resource Controllers ของ Laravel ศึกษาได้ที่ลิ้งนี้ Resource Controllers

บทความต่อไปจะเป็นเรื่องอะไร คอยติดตามกันนะครับ

Laravel รู้จัก Routes และ การรับค่าจาก HttpRequest EP4

การพัฒาน web application ข้อที่ควรคำนึงถึงคือเรื่อง url (endpoints) ต่าง ๆ ภายใน application ของเรา อาจจะเป็น url แบบเดียวกันได้เพียงแต่กำหนด http method ไม่เหมือนกัน เช่น

  • [GET] /api/v1/user สร้างเพื่อ provide data ให้กับ client
  • [POST] /api/v1/user สร้างเพื่อกำหนดไว้สำหรับ create data ใหม่

จุดสั่งเกตุที่เห็นได้ชัดคือ ความเหมือนกันของ url แต่ต่างกันในเรื่อง method เท่านั้น ทางฝั่ง application เราเอง (กำหนด route) จะ mapping route ให้ตรงกับการ request เรียกเข้ามา request เรียกมาแบบ [GET] ก็จะเข้าไปทำงานในส่วนของ GET method , เรียกมาแบบ [POST] จะเข้าไปทำงานในส่งของ POST method เรื่องเหล่านี้ต้องมาทำความเข้าใจกัน บทความนี้จะมาอธิบาย

การกำหนด Methods มีกี่แบบ Laravel จะต้องเขียนอย่างไร

  • GET
    • มีการเก็บ cached การ request ได้
    • ไม่ควรอย่างยิ่งสำหรับการส่ง sensitive data มากับ url
    • มีข้อจำกัดเรื่องความยาวของ url + parameters
    • ใช้สำหรับขอข้อมูลเท่านั้น ไม่เหมาะกับการแก้ไขข้อมูล
  • POST
    • ไม่ถูกเก็บ cached เอาไว้ (request body) จะไม่ถูกเก็บไว้
    • ไม่อยู่ในประวัติของ browser
    • request body สามารถกำหนดได้ยาว ขนาดใหญ่
  • PUT
    • เป็นลักษณะการสร้าง (created) หรือ อัพเดท (update) ข้อมูลฝั่ง serve
  • HEAD
    • คล้ายกันกับ GET แต่ไม่มีการคืนค่ากลับ
  • DELETE
    • การลบข้อมูลที่เป็น unique โดยส่วนใหญ่จะเป็น uuid หรือ id ที่ระบุเป็นเฉพาะเจาะจง
  • PATCH
    • เพื่อปรับเปลี่ยนบางอย่างของข้อมูลฝั่ง server
  • OPTIONS
    • เพื่ออธิบายการทำงานของ data ฝั่ง server

Laravel มีการกำหนด http methods Laravel Routing จะถูกสร้างไว้ที่ไฟล์ /routes/web.php และ /routes/api.php

[GET] สำหรับ inline function
Route::get('foo', function () {
    return 'Hello World';
});
[GET] สำหรับ use controller route อื่น ๆ
Route::get('/user', '[email protected]');
[*] ลักษณะการกำหนด methods อื่น ๆ ทำตาามนี้
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);

การกำหนด parameters ให้ทำ route ในรูปแบบต่าง ๆ

[required] การกำหนด parameters บังคับ ในรูแบบ required parameters จะกำหนดภายใต้ {} 
Route::get('user/{id}', function ($id) {
    return 'User '.$id;
});
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    //
});

[non required] กำหนด parameters แบบไม่บังคับให้ส่งเข้ามา
Route::get('user/{name?}', function ($name = null) {
    return $name;
});
Route::get('user/{name?}', function ($name = 'John') {
    return $name;
});

[regular] กำหนด parameter ด้วย rules ตาม pattern ด้วย regular expression
Route::get('user/{name}', function ($name) {
    //
})->where('name', '[A-Za-z]+');
Route::get('user/{id}', function ($id) {
    //
})->where('id', '[0-9]+');
Route::get('user/{id}/{name}', function ($id, $name) {
    //
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

ศึกษาเพิ่มเติมเรื่อง Routes การทำ Routes Grouping จากลิ้งนี้ Laravel Routes

การรับค่าจาก HttpRequest ในรูปแบบต่าง ๆ

การที่จะได้มาของ parameters data ที่ได้จากการ request client นั้นมีได้หลายรูปแบบ ตามตัวอย่าง

การเข้าถึงค่าของ url information ที่ถูก request เข้ามา

[url path]
$uri = $request->path();

// Without Query String...
$url = $request->url();

// With Query String...
$url = $request->fullUrl();

การรับค่า (request body data) จาก HttpRequest (Payload Request Body)

[all] จะได้ค่าเป็น object ของ request data
$input = $request->all();

[name] การเข้าถึง value ด้วยการกำหนดจาก name ของ parameter
$name = $request->input('name');

[empty then value] กำหนดค่า default หากไม่พบค่าจาก parameters นั้น ๆ 
$name = $request->input('name', 'Sally');

[json array] การเข้าถึง request ที่เป็นลักาณะ raw json array ก็ทำได้
$name = $request->input('products.0.name');
$names = $request->input('products.*.name');

การรับค่า (request body data) จาก HttpRequest (Query Parameters)

[?name=hello] การเข้าถึง queryString จาก parameters
$name = $request->query('name');

[empty then value]
$name = $request->query('name', 'Helen');

ตรวจสอบค่าจาก parameters ด้วย name

[checking] ตรวจสอบ parameter ชื่อ name หากมีจะเข้าเงื่อนไขการทำงาน
if ($request->has('name')) {
    //
}
if ($request->has(['name', 'email'])) {
    //
}
if ($request->hasAny(['name', 'email'])) {
    //
}

[not empty] ตรวจสอบค่า parameter ต้องไม่เป็นค่าว่าง จะใช้ function filled ตรวจสอบ
if ($request->filled('name')) {
    //
}

รูปแบบการใช้งาน Routes และ HttpRequest ใน Laravel มีเนื้อหาเพิ่มเติมอีกมากมาย สามารถศึกษาเพิ่มเติมได้ที่ Laravel Route ,Laravel Request

ขอบคุณที่ติดตามบทความครับ

Laravel ใช้งาน views blade template engine EP3

การพัฒนาเว็บไซต์ด้วยชุด framework ต่าง ๆ จะให้ได้รับความนิยมของ framework นั้น ๆ ต้องมีความพร้อมในเรื่องการจัดการ view ก็คือเครื่องมือการพัฒนาในส่วนของงานการแสดงผลของ data ต่าง ๆ การที่ laravel มาพร้อมกับ blade template engine นี้ถือว่าครบเพียบพร้อม เหมาะแก่การใช้งานเป็นอย่างมาก บทความนี้จะพาไปรู้จักการใช้งาน blade template engine นี้กันว่ามีการทำงานร่วมกับ laravel อย่างไรได้บ้าง

การใช้งาน Blade เพื่อ displaying data จาก controller

การที่จะนำค่าที่ผ่านกระบวนการทำงานจาก controller ไปที่ views (*.blade.php) โดยไฟล์ *.blade.php ทั้งหมดจะถูกจัดเก็บไว้ที่ resources/views/*

  • สร้างไฟล์ {viewname}.blade.php ตัวอย่าง views/example/datatype.blade.php
<html>

<body>
  <h1>DataType</h1>
  <h3>String</h3>
  <p>{{$string}}</p>
  <hr />
  <h3>Boolean</h3>
  <p>{{$boolean}}</p>
  <hr />
  <h3>Integer</h3>
  <p>{{$integer}}</p>
  <hr />
  <h3>Object</h3>
  <p>{{$object['content']}}</p>
  <hr />
  <h3>Array</h3>
  <p>
    <ol>
      @foreach($array as $index=> $item)
      <li>{{$item['content']}}</li>
      @endforeach
    </ol>
  </p>
  <hr />
</body>

</html>
  • แก้ไขไฟล์ routes/web.php กำหนด view(‘datatype’,[?]) กำหนด parameter data ที่จะส่งไปที่ view ใน arg ที่ 2 [?]
Route::get('/datatype', function () {
  return view('/example/datatype', [
    'string' => 'Laravel ใช้งาน view (blade template), json ,pdf',
    'boolean' => true,
    'integer' => 9999,
    'object' => [
      'content' => 'Laravel ใช้งาน view (blade template), json ,pdf EP3',
      'author' => 'poolsawat'
    ],
    'array' => [
      ['content' => 'Laravel เริ่มติดตั้งและสร้าง route ง่าย ๆ EP1'],
      ['content' => 'Laravel แนะนำโครงสร้างภายในโปรเจค อธิบายแต่ละส่วนการทำงาน EP2'],
      ['content' => 'Laravel ใช้งาน view (blade template), json ,pdf EP3'],
    ]
  ]);
});

การใช้งาน blade ในกรณีต้องการใช้งาน control structures (IF ,ELSE ,SWITCH ,LOOP (for ,foreach ,while) ต่าง ๆ เหล่านี้ ทำอย่างไร

ในบางครั้ง บางกรณีที่ต้องการตะเช็คเงื่อนไขในการที่จะแสดงผลของ data หรืออยากที่จะแสดงผลค่าที่เป็นในลักษณะชุดข้อมูล (array) ทาง blade ก็มีคำสั่งมาให้พร้อมแล้ว โดยการที่จะเรียกคำสั่งนั้น จะมี tag @ ตามด้วย name เช่น @if ,@for เป็นต้น

  • สร้างไฟล์ views/example/control.blade.php
<!DOCTYPE html>
<html lang="en">

<body>
  <h1>Control Structures</h1>
  <p>IF ,ELSEIF ,ELSE ,LOOP (FOR,FOREACH,WHILE) ,SWITCH</p>
</body>
<h1>IF</h1>
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif
<hr />
<h1>SWITCH</h1>
@switch($case)
@case(1)
First case...
@break

@case(2)
Second case...
@break

@default
Default case...
@endswitch
<hr />
<h1>LOOP</h1>
<h3>FOR</h3>
@for ($i = 0; $i
< 4; $i++) <br />The current value is {{ $i }} @endfor
<h3>FOREACH</h3>
@foreach ($records as $rec)
<p>This is record {{ $rec }}</p>
@endforeach
<hr />
<h3>FORELSE</h3>
@forelse ($records as $rec)
<li>{{ $rec }}</li>
@empty
<p>No users</p>
@endforelse
<hr />
<h3>WHILE</h3>
@php
$i = 0
@endphp
@while ($i < 4) <p>I'm looping forever.</p>
  @php
  $i++
  @endphp
  @endwhile

</html>
  • แก้ไขไฟล์ routes/web.php โดยเพิ่ม route “control-structures” เข้าไป
Route::get('/control-structures', function () {
  return view('example/control', [
    'records' => [1, 2, 3, 4],
    'case' => 2
  ]);
});

การใช้งาน blade กับการทำ component แยกส่วน

framework ที่ได้รับความนิยมในปัจจุบัน React , Angular ,Vue มีจุดเด่นเรื่องการทำ Component management โดยทั้งสิ้น Laravel เองก็มี concept นี้ด้วยเช่นกัน (ทีมเดียวกับที่พัฒนา Vue) ถือเป็นข้อดีอีกอย่างที่อยากแนะนำให้ใช้งาน laravel

  • สร้างไฟล์ views/example/component.blade.php , views/components/alert.blade.php ,views/components/message.blade.php ,views/components/message-slot.blade.php ตาลำดับ
<!-- views/example/component.blade.php -->
<!DOCTYPE html>
<html lang="en">

<body>
  <h1>Component</h1>
  <h3>empty data</h3>
  @component('components/message',[])
  @endcomponent
  <hr />
  <h3>has data</h3>
  @component('components/message',['name' => 'poolsawat.com'])
  @endcomponent
  <hr />
  <h3>append slot</h3>
  @component('components/message-slot',[])
  <div>element slot 1</div>
  <div>element slot 2</div>
  @endcomponent
  <hr />
  <x-alert />
</body>

</html>
<!-- views/components/alert.blade.php -->
<h1>x-{component}</h1>
<!-- views/components/message.blade.php -->
<h2>
  @if (empty($name))
  component message ...
  @else
  component message {{$name}}
  @endif
</h2>
<!-- views/components/message-slot.blade.php -->
<h2>
  component message append slot
  {{ $slot }}
</h2>
  • แก้ไขไฟล์ routes/web.php เพิ่ม route “components”
Route::get('/components', function () {
  return view('example/component', []);
});
  • ดูผลลัพธ์ที่
  • components ของ laravel มีรูปแบบการเรียกได้ 2 วิธี
    1. @component(‘components/alert’,[]) @endcomponent สามารถเรียกได้แบบนี้จากหน้า views
    2. <x-alert /> สามารถเรียกได้แต่ต้องมีไฟล์ app/View/Components/Alert.php โดย genarate Component ได้ด้วยคำสั่ง artisan package นี้ php artisan make:component Alert
<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    /**
     * Create a new component instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the view / contents that represent the component.
     *
     * @return \Illuminate\View\View|string
     */
    public function render()
    {
        return view('components.alert');
    }
}

เหล่านี้คือการเรียกใช้งาน blade template engine ง่าย ๆ ยังมีความสามารถอีกเยอะมาก ศึกษาเพิ่มเติ่มที่นี่

Laravel แนะนำโครงสร้างภายในโปรเจค อธิบายแต่ละส่วนการทำงาน EP2

หลังจากที่ได้ทำการติดตั้งตามบทความนี้แล้ว “Laravel เริ่มติดตั้งและสร้าง route ง่าย ๆ EP1” บทความนี้จะมาอธิบายเพิ่มเติมแต่ละส่วนของโครงสร้างโปรเจคว่ามีหน้าที่ทำอะไร และตัวอย่างการใช้งานอย่างไรได้บ้าง

มาทำความรู้จักโครงสร้างของ Laravel กัน

  • app/Console
  • app/Exceptions จัดการ Error Exception จากการทำงานที่ผิดพลาด
  • app/Http
    • app/Http/Controllers จัดเก็บ Controllers file (*Controller.php) สำหรับเก็บ Logic ,Route ,Repository ,ORM ต่าง ๆ จะเขียนในไฟล์เหล่านี้
    • app/Http/Middleware จัดเก็บไฟล์ middleware เข้าออกจอง Routes ต่าง ๆ เช่น การทำ Basic Authenticate ,Session Handlers เป็นต้น
    • app/Kernel
  • app/Models เก็บ Model จาก databases ต่าง ๆ กำหนด
  • app/Providers เก็บไฟล์ Providers กำหนด เช่น BroadcasProvider ,EventServiceProvider ,RouteServiceProvider เป็นต้น
  • bootstrap
    • bootstrap/cache
    • bootstrap/app.php
  • config
    • config/app.php เก็บค่า config ระดับ app เช่น ชื่อ app ,env (production) ,timezone UTC เป็นต้น
    • config/auth.php
    • config/broadcasting.php
    • config/cache.php
    • config/cors.php
    • config/databases.php เก็บ database driver ต่าง ๆ mysql , pgsql, sqlsrv ,sqlite เป็นต้น
    • config/filesystems.php
    • config/hashing.php
    • config/logging.php
    • config/mail.php
    • config/queue.php
    • config/services.php
    • config/session.php
    • config/view.php
  • database
    • database/factories
    • database/migrations เก็บไฟล์ script create table ของ database
    • database/seeders
  • public หลังจาก compile laravel mix จะถูกมาเก็บในนี้ ไฟล์ที่นี่
    • public/.htaccess
    • public/favicon.ico
    • public/index.php
    • public/rebots.txt
  • resources
    • resources/css เก็บไฟล์ css ( Cascading Style Sheets ) ต่าง ๆ
    • resources/js เก็บไฟล์ js (Javascript ) ต่าง ๆ
    • resources/lang
    • resources/views เก็บไฟล์ view (*.blade) ต่าง ๆ
  • routes
    • routes/api.php จัดการ route RESTApis ต่าง ๆ (laravel –resource )
    • routes/channels.php
    • routes/console.php
    • routes/web.php จัดการ route ทั่วไป แบบปกติจะจัดการที่ไฟล์นี้เป็นส่วนใหญ๋
  • storage
    • storage/app/public
    • storage/framework
    • storage/logs
  • tests เขียน unittest ต่าง ๆ
    • tests/Feature
    • tests/Unit
  • vender จัดการ libraries ต่าง ๆ ที่ได้จากการติดตั้ง laravel ตั้งแต่เริ่มต้น
  • .env เก็บค่า config ต่าง ๆ โดยจะแบ่งเป็น dev , prod
  • artisan
  • composer.json
  • package.json
  • webpack.mix.js จัดการไฟล์ต่าง ๆ ที่เป็น css ,js ก่อนการ compile minify จะได้ output เก็บที่ public/

ในบ้าง directory ,file ไม่ได้เข้าไปยุ่งเกี่ยวในระหว่างการใช้งาน จะขอไม่อธิบาย หากคุณต้องการศึกษาเพิ่มเติม https://laravel.com/docs/7.x/structure

Laravel เริ่มติดตั้งและสร้าง route ง่าย ๆ EP1

Laravel ปัจจุบันเดินทางมาถึงเวอร์ชั่น 8.X แล้ว ตั้งแต่เวอร์ชั่น 5 จนมาถึงปัจจุบันมีออะไรเปลี่ยนไปค่อนข้างเยอะ ที่เห็นได้ชัด ๆ เรื่องเวอร์ชั่นหลักของ PHP ที่เปลี่ยนจาก 5.X ไปเป็น 7.X เพิ่มความสามารถ ปรับปรุงเรื่องความปลอดภัยและ อื่น ๆ อีกมากมาย

เตรียมเครื่องมือ ทำความรู้จักเบื้องต้น

  • Composer เป็น PPM (PHP Packages management) จัดการ package ในภาษา PHP ส่วนของ Laravel จะให้ในการติดตั้ง global laravel/installers ศึกษาเพิ่มเติมที่นี่
    • composer global require laravel/installer
      • laravel new blog
    • composer create-project –prefer-dist laravel/laravel blog
  • VSCode เครื่องมือใช้สำหรับเขียนโค๊ด laravel ที่จะมี extensions ที่ช่วยเรื่องการเขียน Laravel ให้ดียิ่งขึ้น

เริ่มติดตั้งใช้งานเบื้องต้น

  1. ติดตั้ง composer ให้เรียบร้อย (ข้ามไป ไม่พูดถึงขั้นตอนนี้) จากนั้นตรวจสอบเวอร์ชั่น
    $ composer --version
    > Composer version 1.9.2 2020-01-14 16:30:31
    1. การติดตั้งแบบที่ 1 ติดตั้งด้วยการใช้ laravel cmd ที่ได้จากการติดตั้งจากขั้นตอนนี้
      $ composer global require laravel/installer
      $ laravel new blog
    2. การติดตั้งด้วย composer โดยตรง
      $ composer create-project --prefer-dist laravel/laravel blog
  2. ตัวอย่างใช้คำสั่ง
    $ laravel new poolsawat.com
    > Crafting application... Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 106 installs, 0 updates, 0 removals - Installing doctrine/inflector (2.0.3): Loading from cache - Installing doctrine/lexer (1.2.1): Loading from cache - Installing dragonmantank/cron-expression (3.0.1): Downloading (100%) - Installing voku/portable-ascii (1.5.3): Loading from cache ... > @php -r "file_exists('.env') || copy('.env.example', '.env');" > @php artisan key:generate --ansi [32mApplication key set successfully.[39m > Illuminate\Foundation\ComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: [32mfacade/ignition[39m Discovered Package: [32mfideloper/proxy[39m Discovered Package: [32mfruitcake/laravel-cors[39m Discovered Package: [32mlaravel/tinker[39m Discovered Package: [32mnesbot/carbon[39m Discovered Package: [32mnunomaduro/collision[39m [32mPackage manifest generated successfully.[39m Application ready! Build something amazing.
  3. ตรวจสอบโครงสร้างไฟล์ จะได้หน้าตาแบบนี้

4. ทำการสร้าง route “hello” ที่ไฟล์ route/web.php

5. รันคำสั่ง start server
$ php artisan serve
> Starting Laravel development server: http://127.0.0.1:8000

6. เช็คผลการสร้าง route “hello” ได้ที่ http://localhost:8000/hello

นี่คือแบบที่ง่ายที่สุด สำหรับมือใหม่ บทความต่อ ๆ ไปจะมาเรียนรู้เรื่องอะไรกัน คอยติดตามด้วยนะครับ

ทำความเข้าใจ Date & Time Function MySQL ถ้ารู้แล้วจะทำให้ชีวิตดีขึ้น 10 เท่า

สำหรับข้อมูล Datatype ประเภท Date ,DateTime ,Time ,Timestamp ,Year เหล่านี้ จะทำให้เรามึนงงและเป็นปัญหา และจะจัดการได้ยาก ยกตัวอย่างเคสต่าง ๆ เหล่านี้ เช่น

  • หาวันเวลา ปัจจุบัน
  • ต้องการเปลี่ยน format ของ column Date ให้ส่วนของค่าที่ Query มาได้
  • ต้องการที่จะหาวัน ที่ต้องการเมื่อในอดีตที่ผ่านมา
  • อยากที่จะคำนวนช่วงเวลา นาที ,ชั่วโมง ,วัน ,เดือน หรือ ปีของ Datetime ทั้ง 2 columns ว่า มีช่วงห่างกันเท่าไหร่
  • ต้องการหาวันสุดท้ายของเดือน เช่น 28 ,29 ,30 ,31
  • และอื่น ๆ อีกมากมาย

*** Function ต่าง ๆ ที่จะยกตัวอย่าง บาง function จะมาใน MySQL 8.0 อาจจะไม่ทำงานบน MySQL ที่ต่ำกว่า 8.0 ***

https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date

ลองเล่น Function ต่าง ๆ กัน

Function ที่หา Date จากปัจจุบัน

/* แสดงวันที่ เวลา ปัจจุบัน */
SELECT NOW();  /* '2020-09-22 09:15:09' */
SELECT CURRENT_TIMESTAMP(); /* '2020-09-22 09:22:11' */
/* แสดงวันที่ปัจจุบัน เท่านั้น */
SELECT CURRENT_DATE();  /* '2020-09-22' */
SELECT CURRENT_DATE; /* '2020-09-22' */
SELECT CURDATE(); /* '2020-09-22' */
/* แสดงเวลา ปัจจุบัน เท่านั้น */
SELECT CURRENT_TIME();  /* '09:21:30' */
SELECT CURRENT_TIME; /* '09:21:30' */
SELECT CURTIME(); /* '09:21:30' */
/* แสดงวัน ของแต่ละเดือน */ 
SELECT DAY(NOW()); /* 22 */
SELECT DAYOFMONTH(NOW()); /* 22 */
/ * แสดงลำดับวัน ของสัปดาห์ */
SELECT DAYOFWEEK(NOW());  /* 3 (คือวัน วันอังคาร) */
/* แสดงวันที่ ลำดับ ใน 1 ปี ( 0 - 365 ) */
SELECT DAYOFYEAR(NOW()); /* 266 (2020-09-22)*/

แปลง หรือ เปลี่ยน format ของ Date

/* แสดง format ของ Date ต้นทาง */
SET @SOURCE_DATE = DATE('2020-09-22'); 
SELECT @SOURCE_DATE as origin_date; /* '2020-09-22' */
SELECT DATE_FORMAT(@SOURCE_DATE,'%d/%m/%Y') as dmy_date;  /* '22/09/2020' */
SELECT DATE_FORMAT(@SOURCE_DATE,'%d/%m/%Y %H:%i:%s') as dmyhms_date; /* '22/09/2020 00:00:00' */

หาวันก่อนหน้า (วันที่ในอดีต) หรือวันถัดไป (วันที่ในอนาคต)

SET @SOURCE_DATE = TIMESTAMP('2020-09-22 00:00:00');
SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 MONTH) as add_10_month; /* '2021-07-22 00:00:00' (เพิ่ม 10 วินาที ) */
SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 DAY) as add_10_day; /* '2020-10-02 00:00:00' (เพิ่ม 10 วินาที ) */
SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 HOUR) as add_10_hour; /* '2020-09-22 10:00:00' (เพิ่ม 10 วินาที ) */
SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 MINUTE) as add_10_minute; /* '2020-09-22 00:10:00' (เพิ่ม 10 วินาที ) */
SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 SECOND) as add_10_second; /* '2020-09-22 00:00:10' (เพิ่ม 10 วินาที ) */


SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 MONTH) as sub_10_month; /* '2019-11-22 00:00:00' ( ลบ 10  เดือน)  */
SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 DAY) as sub_10_day; /* '2020-09-12 00:00:00'  (ลบ10  วัน) */
SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 HOUR) as sub_10_hour; /* '2020-09-21 14:00:00' (ลบ 10  ชั่วโมง) */
SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 MINUTE) as sub_10_minute; /* '2020-09-21 23:50:00' (ลบ 10  นาที) */
SELECT DATE_ADD(@SOURCE_DATE , INTERVAL 10 SECOND) as sub_10_second; /* '2020-09-21 23:59:50' (ลบ 10  วินาที )*/

คำนวน ปี ,เดือน ,วัน ,ชั่วโมง ,นาที ,วินาที

SET @BEFORE_DATETIME = TIMESTAMP('2020-08-10 10:10:10');
SET @AFTER_DATETIME = TIMESTAMP('2020-09-20 20:20:20');
SELECT  DATEDIFF(@AFTER_DATETIME,@BEFORE_DATETIME) as diff_year;  /* ห่างกัน 41 วัน */

SELECT TIMESTAMPDIFF(YEAR,@BEFORE_DATETIME,@AFTER_DATETIME) as diff_year; /* ห่างกัน 0 ปี */
SELECT TIMESTAMPDIFF(MONTH,@BEFORE_DATETIME,@AFTER_DATETIME) as diff_month; /* ห่างกัน 1 เดือน */
SELECT TIMESTAMPDIFF(DAY,@BEFORE_DATETIME,@AFTER_DATETIME) as diff_day; /* ห่างกัน 41 วัน */
SELECT TIMESTAMPDIFF(HOUR,@BEFORE_DATETIME,@AFTER_DATETIME) as diff_hour; /* ห่างกัน 994 ชั่วโมง */
SELECT TIMESTAMPDIFF(MINUTE,@BEFORE_DATETIME,@AFTER_DATETIME) as diff_minute; /* ห่างกัน 59650 นาที */
SELECT TIMESTAMPDIFF(SECOND,@BEFORE_DATETIME,@AFTER_DATETIME) as diff_second; /* ห่างกัน 3579010 วินาที */

ตัด ปี ,เดือน ,วัน ,ชั่วโมง ,นาที ,วินาที

SET @TARGET_DATETIME = '2020-09-22 10:15:20';
SELECT DATE(@TARGET_DATETIME) as date_date; /* 2020-09-22 */
SELECT YEAR(@TARGET_DATETIME) as year_date; /* 2020 */
SELECT MONTH(@TARGET_DATETIME) as month_date; /* 9 */
SELECT WEEK(@TARGET_DATETIME) as week_date; /* 38 */
SELECT DAY(@TARGET_DATETIME) as day_date; /* 22 */
SELECT HOUR(@TARGET_DATETIME) as hour_date; /* 10 */
SELECT MINUTE(@TARGET_DATETIME) as minute_date; /* 15 */
SELECT SECOND(@TARGET_DATETIME) as second_date; /* 20 */

สำหรับ Function อื่น ๆ ของ Date & Time ของ MySQL Database สามารถเข้าไปศุกษาเพิ่มเติมได้ที่นี่ “Date and Time Functions”

SpringBoot2 สร้าง CRUD RESTful API พร้อม UnitTest แบบรวดเร็ว

Web Service แบบ RESTful protocol ได้รับความนิยมเป็นอย่างมากในปัจจุบัน หลายภาษามี web framework ของตัวอย่าง Java Spring Framework ก็เช่นกัน

SpringBoot เป็น Framework ที่ได้รับความนิยมมากกับ Java เพราะด้วยความง่ายที่เป็นสิ่งที่ถูกพัฒนาเพื่อแก้ปัญหาในการ Setup Project ที่ค่อนข้างยุ่งยากและซับซ้อนในการ Build RESTful API ขึ้นมาใช้งาน Springboot จึงได้รับความนิยมและได้เปรียบเรื่องความรวดเร็วในการ Setup Project

สิ่งที่จะได้รับเมื่ออ่านบทความบทนี้จบ

  • Create Springboot Project
  • CRUD Data with H2 Database
  • Use GET,POST,PUT,DELETE Methods
  • Content-Type : “application/json”
  • MockMvc
  • CrudRepository Interface crud



เริ่มกันเลย

  1. init project https://start.spring.io/
    1. 1
  2. เพิ่ม h2database ในไฟล์ pom.xml
    1. <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
      
        <groupId>com.poolsawat</groupId>
        <artifactId>MediumTestRestful</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
      
        <name>MediumTestRestful</name>
        <url>http://maven.apache.org</url>
      
        <properties>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          <java.version>1.8</java.version>
        </properties>
      
        <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>2.0.5.RELEASE</version>
        </parent>
      
      
        <dependencies>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
          </dependency>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
          </dependency>
          <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
          </dependency>
        </dependencies>
      
      
        <profiles>
          <profile>
            <id>DEV</id>
            <build>
              <plugins>
                <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
              </plugins>
            </build>
          </profile>
          <profile>
            <id>TEST</id>
            <build>
              <plugins>
                <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-surefire-plugin</artifactId>
                  <version>2.15</version><!--$NO-MVN-MAN-VER$ -->
                </plugin>
              </plugins>
            </build>
          </profile>
      
        </profiles>
      
      </project>
      
  3. สร้าง Project Structure ตามนี้
    1. ไฟล์ CrudController.java
      1. package com.poolsawat.medium.testrestful.controller;
        
        import java.util.Optional;
        
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Controller;
        import org.springframework.web.bind.annotation.DeleteMapping;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.PathVariable;
        import org.springframework.web.bind.annotation.PostMapping;
        import org.springframework.web.bind.annotation.PutMapping;
        import org.springframework.web.bind.annotation.RequestBody;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.ResponseBody;
        
        import com.poolsawat.medium.testrestful.entity.Blog;
        import com.poolsawat.medium.testrestful.repository.BlogRepository;
        
        @Controller
        public class CrudController {
          
          @Autowired
          private BlogRepository blogRepository;
          
          
          @RequestMapping("/")
            public @ResponseBody String greeting() {
                return "Hello World";
            }
          
          @GetMapping("/get")
          public @ResponseBody Iterable<Blog> getBlogs(){
            return this.blogRepository.findAll();
          }
          
          @GetMapping("/get/id/{id}")
          public @ResponseBody Optional<Blog> getBlog(@PathVariable(name="id") Long id){
            return this.blogRepository.findById(id);
          }
          
          @PostMapping("/save")
          public @ResponseBody Blog saveBlog(@RequestBody Blog blog){
            return this.blogRepository.save(blog);
          }
          
          @PutMapping("/update")
          public @ResponseBody Blog updateBlog(@RequestBody Blog blog){		
            return this.blogRepository.save(blog);
          }
          
          @DeleteMapping("/id/{id}")
          public @ResponseBody Long deleteBlog(@PathVariable(name="id") Long id) {
            this.blogRepository.deleteById(id);
            return id;
          }
          
        }
        



    2. ไฟล์ Blog.java
      1. package com.poolsawat.medium.testrestful.entity;
        
        import java.io.Serializable;
        
        import javax.persistence.Entity;
        import javax.persistence.GeneratedValue;
        import javax.persistence.GenerationType;
        import javax.persistence.Id;
        
        @Entity
        public class Blog implements Serializable{
          /**
           * 
           */
          private static final long serialVersionUID = 6833355522200232153L;
        
        
          @Id
          @GeneratedValue(strategy = GenerationType.AUTO)
          private Long id;
            
          private String title;
          
          private String content;
          
          private String author;
          
        
          public Blog() {
            super();
            // TODO Auto-generated constructor stub
          }
        
          public Blog(Long id, String title, String content, String author) {
            super();
            this.id = id;
            this.title = title;
            this.content = content;
            this.author = author;
          }
        
          public Long getId() {
            return id;
          }
        
          public void setId(Long id) {
            this.id = id;
          }
        
          public String getTitle() {
            return title;
          }
        
          public void setTitle(String title) {
            this.title = title;
          }
        
          public String getContent() {
            return content;
          }
        
          public void setContent(String content) {
            this.content = content;
          }
        
          public String getAuthor() {
            return author;
          }
        
          public void setAuthor(String author) {
            this.author = author;
          }
        
          @Override
          public String toString() {
            return "Blog [id=" + id + ", title=" + title + ", content=" + content + ", author=" + author + "]";
          }
          
          
        }
        
    3. ไฟล์ BlogRepository.java
      1. package com.poolsawat.medium.testrestful.repository;
        
        import java.util.List;
        
        import org.springframework.data.repository.CrudRepository;
        import org.springframework.stereotype.Repository;
        
        import com.poolsawat.medium.testrestful.entity.Blog;
        
        @Repository
        public interface BlogRepository extends CrudRepository<Blog, Long> {	
          List<Blog> findByTitle(String title) throws Exception;
          List<Blog> findByAuthor(String author) throws Exception;
        }
        
    4. ไฟล์ Application.java
      1. package com.poolsawat.medium.testrestful;
        
        import org.springframework.boot.CommandLineRunner;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.context.annotation.Bean;
        
        import com.poolsawat.medium.testrestful.entity.Blog;
        import com.poolsawat.medium.testrestful.repository.BlogRepository;
        
        @SpringBootApplication
        public class Application {
        
          public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
          }
          
          @Bean
          public CommandLineRunner demo(BlogRepository blogRepository) {
            return (args) -> {				
              blogRepository.save(new Blog(Long.valueOf("1"), "SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.2) “Nuxt Directory Structure”", 
                  "SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.2) “Nuxt Directory Structure”","poolsawat"));
              blogRepository.save(new Blog(Long.valueOf("2"), "SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.1) “Setup Nuxt Project”", 
                  "SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.1) “Setup Nuxt Project”","poolsawat"));
              blogRepository.save(new Blog(Long.valueOf("3"), "สร้าง Project JSF Primeface ด้วย Maven พร้อมกับสอนทำระบบ Template Layout", 
                  "สร้าง Project JSF Primeface ด้วย Maven พร้อมกับสอนทำระบบ Template Layout","poolsawat"));
            };
          }
        }
    5. ไฟล์ CrudControllerTest.java
      1. package com.poolsawat.medium.testrestful;
        
        
        import static org.hamcrest.CoreMatchers.equalTo;
        import static org.hamcrest.Matchers.hasSize;
        import static org.hamcrest.Matchers.nullValue;
        import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
        import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
        import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
        import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
        import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
        
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.http.MediaType;
        import org.springframework.test.context.junit4.SpringRunner;
        import org.springframework.test.web.servlet.MockMvc;
        
        @RunWith(SpringRunner.class)
        @SpringBootTest
        @AutoConfigureMockMvc
        public class CrudControllerTest {
          
          @Autowired
            private MockMvc mockMvc;	
          
          @Test
          public void testShouldSaveBlog() throws Exception {
            String content = "{\"id\" : 4,\"title\" : \"TestRestful\"}";
            this.mockMvc.perform(
                post("/save")
                .content(content)
                .contentType(MediaType.APPLICATION_JSON)
                )
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.title", equalTo("TestRestful")))
                .andExpect(jsonPath("$.content", nullValue()))
                .andExpect(jsonPath("$.author", nullValue()));
          }
          
          @Test
          public void testShouldGetReturnBlogs() throws Exception {
            this.mockMvc.perform(get("/get"))
            .andDo(print())
            .andExpect(status().isOk())        
                .andExpect(jsonPath("$", hasSize(2)));
          }
          
          @Test
          public void testShouldGetReturnBlogById() throws Exception {
            Integer id = 3;
            this.mockMvc.perform(get("/get/id/"+id))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id", equalTo(id)))
                .andExpect(jsonPath("$.title", equalTo("สร้าง Project JSF Primeface ด้วย Maven พร้อมกับสอนทำระบบ Template Layout")));
          }
          
          @Test
          public void testShouldDeleteBlogById() throws Exception {
            Integer id = 1;
            this.mockMvc.perform(delete("/id/"+id))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("$", equalTo(id)));
          }
          
          @Test
          public void testShouldUpdateBlog() throws Exception {		
            String content = "{\"id\" : 2,\"title\" : \"ShouldUpdateBlog\",\"author\" : \"poolsawat\"}";
            this.mockMvc.perform(
                put("/update")
                .content(content)
                .contentType(MediaType.APPLICATION_JSON)
                )
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id", equalTo(2)))
            .andExpect(jsonPath("$.title", equalTo("ShouldUpdateBlog")))
            .andExpect(jsonPath("$.content", nullValue()))
            .andExpect(jsonPath("$.author", equalTo("poolsawat")));
          }
        }
        
  4. สั่ง mvn spring-boot:run
    1. [INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ MediumTestRestful ---
      [INFO] Nothing to compile - all classes are up to date
      [INFO]
      [INFO] <<< spring-boot-maven-plugin:2.0.5.RELEASE:run (default-cli) < test-compile @ MediumTestRestful <<<
      [INFO]
      [INFO]
      [INFO] --- spring-boot-maven-plugin:2.0.5.RELEASE:run (default-cli) @ MediumTestRestful ---
      
        .   ____          _            __ _ _
       /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
      ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
       \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
        '  |____| .__|_| |_|_| |_\__, | / / / /
       =========|_|==============|___/=/_/_/_/
       :: Spring Boot ::        (v2.0.5.RELEASE)
      ...
      ...
      2018-12-05 18:51:43.509  INFO 3096 --- [           main] c.p.medium.testrestful.Application       : Started Application in 10.0
      65 seconds (JVM running for 16.349)
  5. สั่ง mvn test (ไม่ต้อง spring-boot:run แล้ว) เพื่อ run unittest ทดสอบ api CRUD
    1. ...
      [INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 12.749 s - in com.poolsawat.medium.testrestful.CrudContr
      ollerTest
      2018-12-05 19:02:23.789  INFO 5576 --- [       Thread-3] o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework
      [email protected]: startup date [Wed Dec 05 19:02:12 ICT 2018]; root of context hierar
      chy
      2018-12-05 19:02:23.798  INFO 5576 --- [       Thread-3] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFa
      ctory for persistence unit 'default'
      2018-12-05 19:02:23.798  INFO 5576 --- [       Thread-3] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed
       drop of schema as part of SessionFactory shut-down'
      2018-12-05 19:02:23.805  INFO 5576 --- [       Thread-3] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown ini
      tiated...
      2018-12-05 19:02:23.811  INFO 5576 --- [       Thread-3] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown com
      pleted.
      [INFO] 
      [INFO] Results:
      [INFO] 
      [INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
      [INFO] 
      [INFO] ------------------------------------------------------------------------
      [INFO] BUILD SUCCESS
      [INFO] ------------------------------------------------------------------------
      [INFO] Total time:  17.707 s
      [INFO] Finished at: 2018-12-05T19:02:24+07:00
      [INFO] ------------------------------------------------------------------------

  6. github source

SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.2) “Nuxt Directory Structure”

หลังจากที่ได้ทำการ create nuxt project เป็นที่เรียบร้อยแล้ว (ถ้ายังไม่รู้ว่า setup Nuxt อย่างไรเข้าไปติดตามได้ที่บทความนี้ ” SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.1) “Setup Nuxt Project” “ ) สังเกตุว่าภายใต้ project จะเจอ folders และ files ต่าง ๆ  แต่ละส่วนแบ่งการทำงานและมีหน้าที่ต่างกัน

  • assets
    • คือ folder เก็บไฟล์ resources อย่างเช่น image ,javascript ,stylesheet หรือเป็นไฟล์อื่น ๆ ที่ต้องการทำเป็น static resource
    • การเรียกใช้งาน <img src=“~/assets/image.png”>
    • <template>
        <img src="~/assets/image.png">
      </template>
  • components
    • คือ folder เก็บ File components (.vue)
  • layouts
    • ใช้เก็บไฟล์ layout ของ project ภายในจะมีไฟล์ default.vue เป็น template layout  สามารถกำหนด error page กรณีที่มี error status ที่ไม่ใช้ 200 และยังสามารถสร้าง custom layout ในแบบที่เราต้องการได้อีกด้วย
    • <template>
        <div>
          <nuxt/>
        </div>
      </template>
  • middleware
    • เก็บไฟล์ที่เป็น intercepter ของ project ตัวอย่างไฟล์ authentication กรณีทำระบบ login (auth.js) ก็จะสร้างเก็บไว้ที่ folder นี้
    • export default function (context) {
        context.userAgent = context.isServer ? context.req.headers['user-agent'] : navigator.userAgent
      }
  • pages
    • เก็บไฟล์ views (route mapping) ของ project
    •  
    • <template>
        <div class="container">
          <h1 v-if="error.statusCode === 404">Page not found</h1>
          <h1 v-else>An error occurred</h1>
          <nuxt-link to="/">Home page</nuxt-link>
        </div>
      </template>
      
      <script>
      export default {
        props: ['error'],
        layout: 'blog' // you can set a custom layout for the error page
      }
      </script>
  •  plugins
    • เก็บไฟล์ config plugin เพื่อเรียกใช้ plugin ร่วมกับ project เรา เช่น axios เป็นต้น
  • static
    • เก็บไฟล์ static content ที่สามารถเรียกเข้าถึงได้ผ่าน route url ของ project ได้
    • <!-- Static image from static directory -->
      <img src="/my-image.png"/>
      
      <!-- webpacked image from assets directory -->
      <img src="~/assets/my-image-2.png"/>
  • store
    • เก็บไฟล์ data store Vuex Store store เก็บ data ทำ passing data ภายใน project โดยจะมี main file index.js และเรียก import  modules ต่าง ๆ ได้
    • import Vuex from 'vuex'
      
      const createStore = () => {
        return new Vuex.Store({
          state: () => ({
            counter: 0
          }),
          mutations: {
            increment (state) {
              state.counter++
            }
          }
        })
      }
      
      export default createStore
  • nuxt.config.js
    • file config หลักของ Project ใช้ config เรื่อง build ,css ,js ,plugin ,dev ,env ,generate ,head ฯลฯ เป็นต้น
    • const pkg = require('./package')
      
      module.exports = {
        mode: 'universal',
      
        /*
        ** Headers of the page
        */
        head: {
          title: pkg.name,
          meta: [
            { charset: 'utf-8' },
            { name: 'viewport', content: 'width=device-width, initial-scale=1' },
            { hid: 'description', name: 'description', content: pkg.description }
          ],
          link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
        },
      
        /*
        ** Customize the progress-bar color
        */
        loading: { color: '#fff' },
      
        /*
        ** Global CSS
        */
        css: [],
      
        /*
        ** Plugins to load before mounting the App
        */
        plugins: [],
      
        /*
        ** Nuxt.js modules
        */
        modules: [
          // Doc: https://github.com/nuxt-community/axios-module#usage
          '@nuxtjs/axios',
          // Doc:https://github.com/nuxt-community/modules/tree/master/packages/bulma
          '@nuxtjs/bulma'
        ],
        /*
        ** Axios module configuration
        */
        axios: {
          // See https://github.com/nuxt-community/axios-module#options
        },
      
        /*
        ** Build configuration
        */
        build: {
          postcss: {
            preset: {
              features: {
                customProperties: false
              }
            }
          }
          /*
          ** You can extend webpack config here
          */
          // extend(config, ctx) {
          //   // Run ESLint on save
          //   if (ctx.isDev && ctx.isClient) {
          //     config.module.rules.push({
          //       enforce: 'pre',
          //       test: /\.(js|vue)$/,
          //       loader: 'eslint-loader',
          //       exclude: /(node_modules)/
          //     })
          //   }
          // }
        }
      }
      
  • package.json
    • lib dependencies ของ Nuxt Project
    • {
        "name": "poolsawat",
        "version": "1.0.0",
        "description": "My shining Nuxt.js project",
        "author": "pool13433",
        "private": true,
        "scripts": {
          "dev": "nuxt",
          "build": "nuxt build",
          "start": "nuxt start",
          "generate": "nuxt generate",
          "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
          "precommit": "npm run lint"
        },
        "dependencies": {
          "cross-env": "^5.2.0",
          "nuxt": "^2.0.0",
          "@nuxtjs/bulma": "^1.2.0",
          "@nuxtjs/axios": "^5.0.0"
        },
        "devDependencies": {
          "nodemon": "^1.11.0",
          "babel-eslint": "^8.2.1",
          "eslint": "^5.0.1",
          "eslint-loader": "^2.0.0",
          "eslint-plugin-vue": "^4.0.0",
          "eslint-config-prettier": "^3.1.0",
          "eslint-plugin-prettier": "2.6.2",
          "prettier": "1.14.3"
        }
      }

บทความนี้เป็นเพียงรายละเอียดคร่าว ๆ ของแต่ละส่วนของ Project Nuxt ศึกษาเพิ่มเติมได้จากลิ้งนี้ Nuxt Doc

SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.1) “Setup Nuxt Project”

พวกเรามาถึงยุคที่ต้องมีเว็บไซต์ที่เข้าใช้งานแล้วรู้สึกว่ารวดเร็วทันใจเพราะเมื่อมีอะไรมาขัดจังหวะคนใช้งานจะรู้สึกไม่อยากรอ หรือ อาจจะเลิกสนใจ website ของคุณไปเลย แล้วถ้าอยากจะสร้าง website ลักษณะที่ตอบโจทย์ที่กล่าวมาละ เครื่องมืออะไรจะตอบโจทย์นี้ได้
SPA หรือ single page application คือการที่มี application ที่มีการทำงานในหน้าเดียวกล่าวคือจะไม่มีการ refresh หน้า ถ้าในสมัยเก่าก่อนวิธีการที่นิยมที่สุดก็คงเป็นการใช้ AJAX ในการ รับ-ส่งค่าข้อมูลระหว่างกันโดยเมื่อมีข้อมูลหน้าเว็บเปลี่ยนก็สั่งให้ DOM เปลี่ยนแปลงโดยใช้งานร่วมกับ jQuery พระเอกตลอดการของเรานั้นเอง ฟังดูยากสำหรับเมื่อก่อน
SPA ก็มี framework ในการพัฒนา 3 ค่ายดัง ๆ อย่าง React(Facebook) ,Angular(Google), Vue(ทีมพัฒนาอิสระ) ซึ่ง 3 framework ก็ล้วนเป็น open source แล้วทั้งสิน ถ้าเมื่อจะเลือกเครื่องมืออะไรสักอย่างก็ต้องเข้าไปใช้งาน หรือต้องได้ลองเล่น และเมื่อเกิดปัญหา หรือรู้สึกว่าติดขัดก็จะเลิกและเปลี่ยนไปใช้งานตัวถัดไป และเมื่อถ้าได้ลองจะครบทั้ง 3 framework แล้วตัวเราเองก็จะเลือกได้เองว่าจะเลือก framework ตัวใด
Vue คือ framework ระดับแรก ๆ ที่นักพัฒนามือใหม่เลือกใช้งาน ด้วยเหตุผลเรื่องความง่ายในการติดตั้งไม่จำเป็นต้องติดตั้ง build tool ก็สามารถเริ่มต้นใช้งานได้
Nuxt.js เป็น framework ที่พัฒนาต่อยอดจาก VueJS เพื่อช่วยแก้ปัญหาสำหรับ website ที่ต้องการทำเรื่อง SEO(Search Engine Optimization) เพื่อในพบการค้นหาใน google ได้ง่ายยิ่งขึ้น
เริ่มต้นใช้งาน
เริ่มแรกติดตั้ง Nuxt.js

  1. ติดตั้ง npx (ไม่ขอกล่าวถึงสำหรับขั้นตอนการติดตั้งส่วนนี้) หรือจะใช้ yarn ก็ได้ตามความถนัด
  2. npx: create nuxt project
    npx create-nuxt-app <project-name>
  3. yarn: create nuxt project
    yarn create nuxt-app <project-name>
  4. ใส่ชื่อ project? Project name (poolsawat)
  5. ใส่คำอธิบาย? Project description (My shining Nuxt.js project)
  6. เลือก integrate ร่วมกับ framework อื่น เลือก None (Nuxt default server)
    > none
    express
    koa
    adonis
    hapi
    feathers
    micro
  7. เลือก ui framework
    > none
    bootstrap
    vuetify
    bulma
    tailwind
    element-ui
    buefy
  8. Single Page App เลือก mode website SEO เลือก Universal
    > Universal
    SPA
  9. website ที่มีการต่อเรียก api ก็ให้ติดตั้ง axios ด้วย
    no
    > yes
  10. เพิ่มการตรวจสอบ source code ด้วย ESLint
    no
    > yes
  11. เพิ่มให้มีการจัด format code หลัง save
    no
    > yes
  12. รอ…
  13. ทำตาม stepcd poolsawat
    npm run dev

ติดตั้งเรียบร้อยลอง start application ดู

d:\Blogger\nuxt\poolsawat>npm run dev

> [email protected] dev d:\Blogger\nuxt\poolsawat
> nuxt

INFO Building project

√ success Builder initialized
√ success Nuxt files generated

READY Listening on http://localhost:3000

เสร็จสิ้นเป็นที่เรียบร้อย ตอนหน้าจะพาไปแนะนำโครงสร้างภายใน Nuxt.js กันว่าแต่ละส่วนใช้ทำงานอะไรบ้าง และถ้าจะสร้าง page ใหม่จะต้องทำอย่างไร

สร้าง Project JSF Primeface ด้วย Maven พร้อมกับสอนทำระบบ Template Layout

Java Server Faces (JSF) เป็นเว็บแอ็พพลิเคชันที่ใช้ Java ซึ่งมีจุดมุ่งหมายเพื่อลดความซับซ้อนในการพัฒนาอินเทอร์เฟซสำหรับผู้ใช้บนเว็บ JavaServer Faces เป็นเทคโนโลยีการแสดงผลแบบมาตรฐานซึ่งได้รับการประกาศไว้ในข้อกำหนดผ่านกระบวนการ Java Community Process

JSF ถูกนำไปพัฒนาต่อยอดความสามารถโดยบริษัทขนาดใหญ่ หลายบริษัท

  • Apache MyFaces – The Apache Foundation JSF implementation with Ajax components
  • Backbase Enterprise Ajax – JSF Edition – Ajax framework
  • BootsFaces Open source JSF Framework based on Bootstrap
  • IBM Notes – XPages
  • ICEfaces – open-source, Java JSF extension framework and rich components, Ajax without JavaScript
  • JBoss RichFaces (derived from and replaces Ajax4jsf) – Ajax-enabled JSF components for layout, file upload,
  • forms, inputs and many other features. It reached its end-of-life in June 2016.
  • OmniFaces – open-source JSF utility library
  • Open Faces – Ajax framework with JSF components
  • Oracle ADF Faces Rich Client – Oracle Application Development Framework
  • PrimeFaces – Ajax framework with JSF components
  • Sun Java BluePrints AJAX components
  • ZK – Ajax framework with JSF components





Primeface เป็น Framework ที่ไม่หยุดพัฒนาและมี UI Component มากมายและสวยงาม

ด้วยความสม่ำเสมอในการพัฒนาและมีการปรับปรุง framework อยู่ตลอดเวลา ออกผลิตภัณฑ์ตัวใหม่ออกมา เช่น PrimeNG ,PrimeReact ,PrimeUI เป็นต้น ทำให้เลือก Primeface เป็นตัวเลือกในการพัฒนา Project Webapp นี้

มาเริ่มสร้าง Project กัน

  1. Build Maven Project (maven-archetype-webapp) ขึ้นมา
  2. เพิ่ม Dependencies
  3. สร้างไฟล์ webapp/WEB-INF/faces-config.xml
  4. เพิ่ม Configure Faces Servlet in webapp/WEB-INF/web.xml
  5. สร้างไฟล์ webapp/templates/layout.xhtml
  6. สร้างไฟล์ webapp/templates/themeMenu.xhtml เพื่อทำเป็น navigator Menu ลิ้งไปที่หน้าอื่น ๆ
  7. สร้าง page webapp/pages/page1.xhtml ,page2.xhtml ,page3.xhtml ,page4.xhtml

    1. จุดที่สำคัญคือเพิ่ม template=”../templates/layout.xhtml” เข้าไปเพิ่มที่ ui:composition ทำให้อ้างถึง template layout ได้
  8. การเปลี่ยน Primeface Theme ของ website

  9. ตัวอย่างหน้า web site

DynamicReport Engine สร้างครั้งเดียวออก Report ได้ทุกแบบ (Hybrid Report)

โปรเจคเกือบจะทุกระบบที่พัฒนาขึ้นมาจำเป็นต้องมองหาระบบออกรายงาน เพื่อดูรูปแบบมิติของข้อมูลในมุมมองต่าง ๆ และรูปแบบชนิดของไฟล์หลากหลายชนิด เช่น PDF , XLS ,HTML , DOCX ,PPTX , CSV , TEXT , XML เป็นต้น ถ้าความต้องการของลูกค้าอยากจะได้ทุกชนิดละคงต้องประเมินเวลาไปอย่างน้อย 1 เดือนสำหรับการที่ทำ Report ให้ออกมาตามประเภทไฟล์ข้างต้นนี้ทั้งหมด แต่ลูกค้าที่น่ารักใจร้อนอยากจะได้เร็ว ๆ ให้เวลาช้าที่สุด 1 สัปดาห์ได้ไหม บทความนี้ช่วยคุณแก้ปัญหานี้ได้แน่นอน

สำหรับบทความนี้ก็เหมาะสำหรับนักพัฒนาที่มีพื้นฐาน Java สักหน่อย เริ่มกันเลย

    1. สร้าง Maven Project ขึ้นมา (จะเป็น Java Web หรือ Java Application ก็แล้วแต่ลักษณะงานของคุณ)
    2. เพิ่ม .pom ตามนี้
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <groupId>com.poolsawat</groupId>
          <artifactId>MyDynamicReport</artifactId>
          <version>1.0-SNAPSHOT</version>
          <packaging>jar</packaging>
          <properties>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              <maven.compiler.source>1.7</maven.compiler.source>
              <maven.compiler.target>1.7</maven.compiler.target>
              
              <slf4j.version>1.7.7</slf4j.version>
              
              <jasperreports.version>6.3.1</jasperreports.version>
              <dynamicreport.version>5.0.0</dynamicreport.version>
              <groovy.version>1.8.6</groovy.version>
              <poi.version>3.10.1</poi.version>
              
          </properties>
          <dependencies>        
      
              <dependency>
                  <groupId>org.codehaus.groovy</groupId>
                  <artifactId>groovy-all</artifactId>
                  <version>${groovy.version}</version>
              </dependency>        
              <dependency>
                  <groupId>net.sourceforge.dynamicreports</groupId>
                  <artifactId>dynamicreports-core</artifactId>
                  <version>${dynamicreport.version}</version>
              </dependency>        
              <dependency>
                  <groupId>net.sf.jasperreports</groupId>
                  <artifactId>jasperreports</artifactId>
                  <version>${jasperreports.version}</version>
                  <exclusions>
                      <exclusion>
                          <groupId>commons-logging</groupId>
                          <artifactId>commons-logging</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              
              <!--Poi-->
              <dependency>
                  <groupId>org.apache.poi</groupId>
                  <artifactId>poi</artifactId>
                  <version>${poi.version}</version>
              </dependency>
              
              
              <!-- Logging -->
              <dependency>
                  <groupId>org.slf4j</groupId>
                  <artifactId>slf4j-api</artifactId>
                  <version>${slf4j.version}</version>
                  <scope>compile</scope>
              </dependency>
              <dependency>
                  <groupId>org.slf4j</groupId>
                  <artifactId>slf4j-log4j12</artifactId>
                  <version>${slf4j.version}</version>
              </dependency>
              <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
              <dependency>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
                  <version>1.2</version>
              </dependency>
      
          </dependencies>
      </project>




  1. สร้าง GenarateReport.java
    package com.poolsawat.dynamic;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.util.Date;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import static net.sf.dynamicreports.report.builder.DynamicReports.*;
    import net.sf.dynamicreports.report.builder.style.StyleBuilder;
    import net.sf.dynamicreports.report.constant.HorizontalTextAlignment;
    import net.sf.dynamicreports.report.datasource.DRDataSource;
    import net.sf.dynamicreports.report.exception.DRException;
    import net.sf.jasperreports.engine.JRDataSource;
    
    /**
     *
     * @author poola410
     */
    public class GenarateReport {
    
        public GenarateReport() {
            build();
        }
    
        private void build() {
            try {
                report()//create new report design
                        //.setDefaultFont(stl.font().setFontName("TH SarabunPSK").setFontSize(16))                    
                        .columns(
                                col.column("PostId", "id", type.integerType())
                                        .setStyle(getTableStyle())
                                        .setTitleStyle(getTableStyle()),
                                col.column("PostTitle", "title", type.stringType())
                                        .setStyle(getTableStyle())
                                        .setTitleStyle(getTableStyle()),
                                col.column("PostAuthor", "author", type.stringType())
                                        .setStyle(getTableStyle())
                                        .setTitleStyle(getTableStyle()),
                                col.column("PostPublicDate", "public_date", type.dateType())
                                        .setStyle(getTableStyle())
                                        .setTitleStyle(getTableStyle()))
                        .title(cmp.text("PoolsawatBlogs"))//shows report title
                        .pageFooter(cmp.pageXofY())//shows number of page at page footer
                        .setDataSource(createDataSource())//set datasource
                        //.show();//create and show report
                        .toPdf(new FileOutputStream(new File("./src/main/resources/output/poolsawat.pdf")))
                        .toXls(new FileOutputStream(new File("./src/main/resources/output/poolsawat.xls")))
                        .toCsv(new FileOutputStream(new File("./src/main/resources/output/poolsawat.csv")))
                        .toXlsx(new FileOutputStream(new File("./src/main/resources/output/poolsawat.xlsx")))
                        .toHtml(new FileOutputStream(new File("./src/main/resources/output/poolsawat.html")))
                        .toPptx(new FileOutputStream(new File("./src/main/resources/output/poolsawat.pptx")))
                        .toText(new FileOutputStream(new File("./src/main/resources/output/poolsawat.text")))
                        .toXml(new FileOutputStream(new File("./src/main/resources/output/poolsawat.xml")));
                        
            } catch (DRException e) {
                e.printStackTrace();
            } catch (FileNotFoundException ex) {
                Logger.getLogger(GenarateReport.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        
        private StyleBuilder getTableStyle(){
            return stl.style().setBorder(stl.border(stl.pen1Point()))
                    .setHorizontalTextAlignment(HorizontalTextAlignment.LEFT);
        }
    
        private JRDataSource createDataSource() {
            DRDataSource dataSource = new DRDataSource("id", "title","author","public_date");
            dataSource.add(1215,"Quartz Scheduler มันคืออะไร การใช้งานเบื้องต้น", "admin", new Date());
            dataSource.add(1150,"Gson Open Source Library สำหรับจัดการ JSON Formatter", "admin", new Date());
            dataSource.add(1132,"สร้าง Spring MVC 4 ร่วมกับ Apache Tiles 3", "admin", new Date());
            dataSource.add(1099,"[ES6] Promise คืออะไร", "admin", new Date());
            dataSource.add(1072,"ประสบการณ์ทำเว็บไซต์ให้ปลอดภัยด้วย HTTPS ไม่ยากอย่างที่คิด", "admin", new Date());
            dataSource.add(1025,"เล่าประสบการณ์แข่งขัน Hackathon ครั้งแรกให้ชีวิต", "admin", new Date());
            dataSource.add(1002,"สรุปสิ่งที่ได้รับจากงาน Cloud Functions for Firebase and Next Generation of Web", "admin", new Date());
            return dataSource;
        }
    
        public static void main(String[] args) {
            new GenarateReport();
        }
    }
    
  2. สั่ง RUN File GenarateReport.java
  3. ดู output ตาม Path

 DynamicReport สามารถทำ Report ที่แสดงในรูปแบบของกราฟได้ด้วย ศึกษาเพิ่มเติมจากลิ้งนี้

Github source ZIP , Github Source Repository