URLからQRコードを生成するアプリを作ろう

## はじめに

初めましての人もそうでない人もこんにちは！

最近就活を始めたという人はそこそこいるのではないでしょうか？
最近ではオンライン面接というものが増えてきていてZOOMやGoogleMeetを使って面接する企業も少なくありません！

おそらくこれを見ているあなたは面接の始めの方で「あなたの自己紹介(自己PR)をお願いします。」と聞かれた経験はあると思います！

自己紹介のやり方は様々ですが私は自己紹介スライドを用意してそのスライドを使って画面共有をしながら紹介をしています！

その中にこのQiita記事やGithubなどのQRコードを入れることでわざわざリンクをコピペせずともカメラ機能だけで見ていただくことができるようになるのでとても良いのではないでしょうか？

「書類選考の時にQiitaとかGithubのURLは出しているよー」という場合は書類選考の時に書くことができなかった他の自己PRできるものをQRコードにすれば良いのではないでしょうか？

しかしインターネット上に転がっているQRコード作成ツールを使って自分の情報が入ったサイトのURLを入れることに抵抗感があるひとは少なからずいるのではないでしょうか？

そこで今回はローカル環境でQRコード作成アプリを作って行きたいと思います！

## 今回の目標
1. サイトのURLからQRコードを作成させる！
2. 作成したQRコードをボタンひとつでコピーやダウンロードができるようにする！
3. デザインはシンプルに!

## 今回扱う技術・言語
### フロントエンド:
React + TypeScript
### バックエンド:
Go

## ファイルディレクトリ
今回作成するアプリの主なディレクトリ構成です！
```
Create_QRcode/
│
├── frontend
│    ├── src
│    │   ├── App.tsx
│    │   ├── App.css
│    │   ├── QRGenerator.tsx
│    │   ├── QRGenerator.css
│    │   └── ...
│    └── ...
└── backend
     ├── main.go
     ├── go.sum
     └── go.mod
```

## 準備
それでは作成するために色々なものをインストールしていきましょう！
### フロントエンド
```
npx create-react-app frontend --template typescript
cd frontend
npm install axios
```
### バックエンド
```
mkdir backend
cd backend
go mod init backend
go get github.com/gin-contrib/cors
go get github.com/gin-gonic/gin
go get github.com/skip2/go-qrcode
```
これで準備完了です！
コーディングしていきましょう！

## コーディング
まずはフロント側から作成していきます！
### フロントエンド
```src/App.tsx
import React from 'react';
import QRGenerator from './QRGenerator';
import './App.css';

const App: React.FC = () => {
  return (
    <div className="App">
      <QRGenerator />
    </div>
  );
};

export default App;
```
```src/App.css
body {
  background-color: #f0f2f5;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

.App {
  width: 100%;
  max-width: 500px;
}
```
```src/QRGenerator.tsx
import React, { useState, useRef } from 'react';
import axios from 'axios';
import './QRGenerator.css';

const QRGenerator: React.FC = () => {
  const [url, setUrl] = useState('');
  const [qrData, setQrData] = useState('');
  const qrRef = useRef<HTMLImageElement>(null);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      const response = await axios.post('http://localhost:8080/generate-qr', { url });
      setQrData(response.data.qr_data);
    } catch (error) {
      console.error('Error generating QR code:', error);
    }
  };

  const handleCopyImage = async () => {
    if (qrRef.current) {
      const canvas = document.createElement('canvas');
      canvas.width = qrRef.current.width;
      canvas.height = qrRef.current.height;
      const ctx = canvas.getContext('2d');
      if (ctx) {
        ctx.drawImage(qrRef.current, 0, 0);
        canvas.toBlob(async (blob) => {
          if (blob) {
            try {
              await navigator.clipboard.write([
                new ClipboardItem({
                  'image/png': blob
                })
              ]);
              alert('コピーしたよ');
            } catch (err) {
              console.error('Failed to copy image: ', err);
              alert('失敗したよ');
            }
          }
        }, 'image/png');
      }
    }
  };

  const handleDownload = () => {
    const link = document.createElement('a');
    link.href = qrData;
    link.download = 'qrcode.png';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  };

  return (
    <div className="qr-generator">
      <h1>QRコードの作成</h1>
      <form onSubmit={handleSubmit} className="qr-form">
        <input
          type="url"
          value={url}
          onChange={(e) => setUrl(e.target.value)}
          placeholder="Enter URL"
          required
          className="qr-input"
        />
        <button type="submit" className="qr-button">生成</button>
      </form>
      {qrData && (
        <div className="qr-result">
          <img ref={qrRef} src={qrData} alt="QR Code" className="qr-image" />
          <div className="qr-actions">
            <button onClick={handleCopyImage} className="qr-action-button">コピー</button>
            <button onClick={handleDownload} className="qr-action-button">ダウンロード</button>
          </div>
        </div>
      )}
    </div>
  );
};

export default QRGenerator;
```
```src/QRGenerator.css
.qr-generator {
    max-width: 500px;
    margin: 2rem auto;
    padding: 2rem;
    background-color: #ffffff;
    border-radius: 10px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    font-family: Arial, sans-serif;
  }
  
  .qr-generator h1 {
    color: #333;
    text-align: center;
    margin-bottom: 1.5rem;
  }
  
  .qr-form {
    display: flex;
    margin-bottom: 1.5rem;
  }
  
  .qr-input {
    flex-grow: 1;
    padding: 0.5rem;
    font-size: 1rem;
    border: 1px solid #ccc;
    border-radius: 4px 0 0 4px;
  }
  
  .qr-button {
    padding: 0.5rem 1rem;
    font-size: 1rem;
    color: #fff;
    background-color: #007bff;
    border: none;
    border-radius: 0 4px 4px 0;
    cursor: pointer;
    transition: background-color 0.3s ease;
  }
  
  .qr-button:hover {
    background-color: #0056b3;
  }
  
  .qr-result {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  
  .qr-image {
    margin-bottom: 1rem;
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    padding: 1rem;
  }
  
  .qr-actions {
    display: flex;
    gap: 1rem;
  }
  
  .qr-action-button {
    padding: 0.5rem 1rem;
    font-size: 0.9rem;
    color: #fff;
    background-color: #28a745;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.3s ease;
  }
  
  .qr-action-button:hover {
    background-color: #218838;
  }
  
  @media (max-width: 600px) {
    .qr-generator {
      padding: 1rem;
    }
  
    .qr-form {
      flex-direction: column;
    }
  
    .qr-input {
      border-radius: 4px;
      margin-bottom: 0.5rem;
    }
  
    .qr-button {
      border-radius: 4px;
    }
  
    .qr-actions {
      flex-direction: column;
    }
  }
```

### バックエンド
```backend/main.go
package main

import (
	"encoding/base64"
	"net/http"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/skip2/go-qrcode"
)

type URLRequest struct {
	URL string `json:"url" binding:"required"`
}

func main() {
	r := gin.Default()

	config := cors.DefaultConfig()
	config.AllowOrigins = []string{"http://localhost:3000"}
	r.Use(cors.New(config))

	r.POST("/generate-qr", func(c *gin.Context) {
		var req URLRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		qr, err := qrcode.Encode(req.URL, qrcode.Medium, 256)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate QR code"})
			return
		}

		base64Encoding := base64.StdEncoding.EncodeToString(qr)
		dataURI := "data:image/png;base64," + base64Encoding

		c.JSON(http.StatusOK, gin.H{
			"qr_data": dataURI,
			"url":     req.URL,
		})
	})

	r.Run(":8080")
}
```

ふう...こんなもんではないでしょうか？
Goはまだまだ使い慣れないので脳への疲労がたまらないです~

## 実行すると・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3631203/fdf39e91-2c20-ea9b-882f-24633ea2596d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3631203/d1450c54-3ce8-2f15-ea2c-afc5abe606db.png)

上手くQRコードを作成することができました！
このQRコードを読み取って何が表示されるか見てみてください！！
(URLから察しはつきますが)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3631203/739436d9-32f5-c9ab-17f2-747bfdf2ccf7.png)

ちゃんとURLを入れないと怒られますよ！

## おわりに
今回はURLからQRコードを生成するアプリを作りました！
これで怪しいQRコード生成ツールを使わなくてすみますね！
まぁそんな知られて困る情報はサイトに出していないはずなので大丈夫だとは思いますが・・・

これにてこの記事を締めさせていただきす！
またどこかの記事でお会いしましょう！

## Github URL
https://github.com/FrohleinYoshie/QRCode_Generator