初学者が欲しいチュートリアル集めたい！「Tutorial List」を作ったお話

[Product Hunt](https://www.producthunt.com/)は、世界最大のプロダクトコミュニティで、毎日その日にローンチされたアプリ、Chromeエクステンション、Webサイトを探すことができて、また、どれが一日で一番人気だったのかが投票で決定されます。私たち個人開発者が見る理由としては、世界でどんな技術が流行っているのかトレンドを知る、似たようなサービスを作ってみようとアイデアを得る、といったのが挙げられます。

そんなわけで、ほぼ日課としてサイトに通い、[気になったプロダクトをTwitterで紹介したり](https://twitter.com/svfreerider)しているのだけど。やっぱり、いち開発者として、この毎日行われているレースに参加してみたいという思いも日に日に募っていきました。

上位に入っているプロダクトはいずれもクオリティが著しく高く、「これ作り切るには時間とお金かかりそうだなー。」と思ったりもするのもだけど、実は数時間で作ったと思われるようなサービスでもランクトップに食い込み、1000以上の票を集めたりすることがあります。

例えば、「[Dark Mode List](http://darkmodelist.com)」。TwitterやRedditで対応しているような夜間モードの一覧をただ集めたサイト。作ったのが、Andreyという界隈で期待のルーキーとして注目されている開発者とはいえ、工数は全くかかっていないはず。（しかも、Sheet 2 Siteというコード要らずで作っている）これは、500以上の票を獲得。

それから、「[Do Things That Don’t Scale](https://dothingsthatdontscale.com)」に「[Open Startups](https://open-startups.xyz)」、いずれも1000近くの票を獲得し、その日一番のプロダクトに輝いています。そう思ってくると、急に「俺にもできるんじゃね？」という闘志が湧いてきました。

そして、同じようなものとしてリリースしたのが、「[MVP List](http://mvplist.co)」。これは、有名スタートアップたちがリリース当時どんな格好だったのか、スクショとそれにまつわる記事のURLをリスト化したものです。それこそ、数時間で作ったのですが、結果は、なんと8票！上位なんて甚だ遠い結果に。

前置きが長くなりましたが、また性懲りもなくこの手のミニサイトを作りました。それが、「[Tutorial List](http://tutoriallist.co)」！前回は票狙いだけで作ったのですが、今回はまあ普通に自分が欲しかったリストを作ろうと思ってローンチしました！

<a href="https://www.producthunt.com/posts/tutorial-list?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-tutorial-list" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=168561&theme=light" alt="Tutorial List - A list of tutorials programmers start learning today | Product Hunt Embed" style="width: 250px; height: 54px;" width="250px" height="54px" /></a>
<img width="100%" alt="qiita.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116375/87b7e62c-e744-db35-5208-74f7871ed86d.png">

## 問題

3年くらい前に、独学でRailsをやり始めました。当時は、スクールに通うようなお金も時間もなく、Railsチュートリアルを何周もやって何とか書けるようになっていきました。

しばらくして、「スマホアプリ作りたい！」ってなり、React Nativeをやり始めました。この時も独学ではあったものの、知り合いに教えてもらったUdemyの教材を一つ買うことにしました。これをやっぱり何周も何周もやり込みました。

思えば、新しいことに向き合う時には、いつもチュートリアル使ってました！そして、何か問題が生じたら、チュートリアルに立ち戻る。チュートリアルでなくても、誰かがMediumやQiitaに備忘してくれてるベストプラクティスがどれだけ役に立ったのか。

しかし、そのチュートリアルやベストプラクティスに限って探すのは結構大変。

## 解決方法

なら、チュートリアルやベストプラクティスに限定して探せるサイトがあったら便利ではないか、と。Googleの検索アルゴリズムに無視された、優良なベストプラクティスだけを集めた、初学者たちの「作りたい！」に答えられるサイトです。

![tutoriallist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116375/7e53c18f-d2db-324d-d916-78889ffd0ba4.png)

Tutorial Listは、初学者でもゼロからアプリを作れるチュートリアルを集めてます。勿論、サイトは無料で利用することができます。とにかく、質にこだわってコンテンツを揃えていく予定です。

## Tutorial Listのサイト構築に使った技術

* React
* Firebase
* Netlify
* AWS S3

## チュートリアル

### 1. Reactアプリを作る

まずは、Reactアプリケーションを作成します。

```
$ yarn create react-app tutoriallist
$ cd tutoriallist
$ yarn start
```

Reactの画面が一旦表示されるのを確認します。

### 2. Firebaseにアプリを登録する

チュートリアルのデータはFirebaseに入れていきました。無料で使えるし、スケールするときも選択肢が多いので、どんな形態にも導入されるようになってきたように思います。

[Firebaseの公式サイト](https://firebase.com)で、アプリの登録を行ないます。

![firebase.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116375/cbad22b4-0d72-6858-e5ae-f0f5ce4fb0b0.png)

### 3. Cloud Firestoreを準備する

データベースはCloudFirestooreを使います。ロックモードとテストモードを選べるのですが、一旦テストモードにして後から下記のように読み取りだけ可能なルールに書き換えれば良いと思います。

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read;
    }
  }
}
```

データベースのカラムはお好みですが、今回は下記のようなカラム形式でチュートリアルのデータを直接書き込んでいきます。

![cloudfirestore.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116375/9f791ee6-c76c-a620-60fe-bd7fbde0d8cb.png)


### 4. FirebaseをReactに導入する

アプリにFirebaseを導入していきます。

```
$ yarn add firebase
```

```React:src/App.js
import React, { Component } from "react";
import firebase from "firebase";
import "./App.css";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      tutorials: []
    };
  }

  async componentDidMount() {
    const tutorials = this.state.tutorials;

    firebase
      .firestore()
      .collection("tutorials")
      .get()
      .then(querySnapshot => {
        querySnapshot.forEach(function(doc) {
          tutorials.push(doc.data());
        });
        this.setState({ tutorials: tutorials });
      });
  }

  render() {
    return (
      <div>
        <p>Hello World!</p>
      </div>
    );
  }
}

export default App;
```

### 5. Home Containerを実装する

HomeのContainerを作成します。

```React:src/components/HomeContainer.js
import React, { Component } from "react";

class HomeContainer extends Component {
  render() {
    return (
      <div>
        <section class="list">
          <div class="wrapper">
            <p>Hello World!</p>
          </div>
        </section>
      </div>
    );
  }
}

export default HomeContainer;
```

App.jsに呼び出します。その際、Firebaseで取得してきたtutorialsを渡します。

```src/App.js
  render() {
    return (
      <div>
        <HomeContainer tutorials={this.state.tutorials} />
      </div>
    );
  }
```

### 6. Tutorialを実装する

Tutorial一つ一つを読み込むComponentを作成します。

```React:src/components/Tutorial.js
import React from "react";

// Load api GET api/cuvee/list
// chua co truong year, name favor_icon , fix main color , favorBackground,
const Tutorial = ({ tutorial, key }) => {
  return (
    <li id={key}>
      <a href={tutorial.url} target="_blank">
        <div class="item">
          <div class="image">
            <img src={tutorial.image} />
            <div class="tag">
              <span>{tutorial.tag}</span>
            </div>
          </div>
          <div class="text">
            <h2>{tutorial.name}</h2>
            <p>{tutorial.tagline}</p>
          </div>
        </div>
      </a>
    </li>
  );
};

export default Tutorial;
```

### 7. Tutorial Listを実装する

Itemを表示するListのContainerを作成し、Tutorialを呼び出します。

```React:src/components/TutorialList.js
import React from "react";
import Tutorial from "./Tutorial";

const TutorialList = ({ tutorials }) => (
  <ul>
    {tutorials.map((tutorial, i) => (
      <Tutorial key={i} tutorial={tutorial} />
    ))}
  </ul>
);

export default TutorialList;
```

Tutorial ListをHome Containerに呼び出します。

```React:src/components/HomeContainer.js
import React, { Component } from "react";
import TutorialList from "./TutorialList";

class HomeContainer extends Component {
  render() {
    return (
      <div>
        <section class="list">
          <div class="wrapper">
            <TutorialList tutorials={this.props.tutorials} />
          </div>
        </section>
      </div>
    );
  }
}

export default HomeContainer;
```

### 8. Netlifyに登録する

最後はNetflifyでデプロイします。GitHubやGitLabでバージョン管理をしていれば、そのレポジトリとブランチを指定するだけでデプロイが完了します。

![netlify.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116375/ed375639-ff19-349a-b131-cf1bdbed8b66.png)

## コンテンツの集め方

実装よりも、コンテンツを集めるのが結構大変でした。Googleで検索して出てきた記事を貼っているだけでは、まるで意味がありませんので。

### 自分が使って良かったもの

まず、自分が使って良かったものを挙げました。最初に挙げた、Rails チュートリアルや、Udemyの教材は勿論ですが、何とか記憶を辿って、要所の実装でお世話になった記事をリストップします。

### 友だちが使って良かったもの

次に、エンジニアの友だちに、「勉強はじめた時に良かった教材や、記事教えてくれない？」といろんな人に連絡しました。友だちはみんなできるエンジニアなので、彼らのおすすめなら信用できます。

### Indie Hackers、Hacker Noon

そして、[Indie Hackers](https://indiehackers.com)や、[Hacker Noon](https://community.hackernoon.com)で「おすすめのチュートリアルを教えてください」と質問して回りました。しかし、なかなか大雑把な質問ということもあって、回答を得られませんでした。そこで、「ブロックチェーンでおすすめの教材は何ですか？」、「機械学習をどうやって学びましたか？」と、絞った質問をしてみることで少しずつ回答を得られるようになりました。

## さいごに

実装自体は、ものの数時間で完了しましたが、コンテンツ集めを一週間くらいかけて、空いた時間作ってやったような感じです。これからは、自分だけでなく、Submitページを用意してサイトを訪れてくれた方からもおすすめを募集していければと考えています。

こんな感じで作ってみたTutorial Listですが、本日付（2019年9月21日）でProduct Huntに参戦中なので、投票・応援のほどよろしくお願いしますー！

<a href="https://www.producthunt.com/posts/tutorial-list?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-tutorial-list" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=168561&theme=light" alt="Tutorial List - A list of tutorials programmers start learning today | Product Hunt Embed" style="width: 250px; height: 54px;" width="250px" height="54px" /></a>

## 関連リンク

* [Tutorial List](https://tutoriallist.co/)
* [Product Hunt](https://www.producthunt.com/posts/tutorial-list)