5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

初心者でも10分で!? AI Coding(IBM Bob)でSNSアプリを作ったらすごすぎた話

Last updated at Posted at 2026-01-06

はじめに

初めてのAI Codingに挑戦してみました。結果、なんとわずか10分でSNSアプリが完成!
私がしたことは、VSCodeを開き、拡張機能のIBM Bobをインストールして「SNSアプリを作りたい」と入力しただけ。
あとは、Bobがコードを書きながら「ここはこういうことをしているんだよ」と解説してくれるのを見守るだけでした。

今回は、Bobがどのくらい本格的なWebアプリを自動生成できるのかを、スクショとコード例とともに紹介します。
初心者でもここまでできる!という驚きと、AI活用のリアルな体験を共有できればと思います。

実装機能の紹介

作成したSNS風Webアプリケーションには、驚くほど充実した機能が備わっています:

  • ✅ ユーザー認証(登録・ログイン)
  • ✅ 投稿の作成・編集・削除
  • ✅ 画像付き投稿
  • ✅ いいね機能
  • ✅ コメント機能
  • ✅ フォロー/フォロワー機能
  • ✅ タイムライン表示
  • ✅ プロフィール編集
  • ✅ ユーザー検索

デモ

機能が充実しているのはもちろんですが、それ以上にUIの美しさが際立っています。
初心者が作ったとは思えない、見た目も使い勝手も整ったアプリに仕上がりました。

①ランディング画面
この画面でログイン・新規登録をします。
image (16).png

②新規登録画面
ユーザー名やメールアドレスなどの個人情報を登録します。
image (21).png

③ログイン画面
新規登録ができたら、ここからログインすることができます。
image (20).png

④投稿画面
Xのように、文章や写真などを投稿することができます。
image (19).png

⑤プロフィール画面
編集ボタンを押すと、背景画像やプロフィール画像、名前、自己紹介などを編集することができます。
image (18).png

⑥タイムライン
フォローした人や自分の投稿が時系列順に表示され、いいねやコメントができます。
image (17).png

技術スタック

  • バックエンド: Node.js v14+, Express v4
  • データベース: MongoDB v4+
  • テンプレートエンジン: EJS
  • 認証: Passport.js (Local Strategy)
  • 画像アップロード: Multer, Cloudinary
  • セッション管理: express-session, connect-mongo
  • その他: bcrypt, method-override, express-validator

プロジェクト構成

sns-app/
├── config/              # 設定ファイル
│   ├── database.js      # MongoDB接続
│   └── passport.js      # Passport認証設定
├── controllers/         # ビジネスロジック
│   ├── authController.js
│   ├── postController.js
│   └── userController.js
├── middleware/          # カスタムミドルウェア
│   └── auth.js
├── models/              # データモデル
│   ├── User.js
│   └── Post.js
├── public/              # 静的ファイル
│   ├── css/
│   ├── js/
│   └── images/
├── routes/              # ルート定義
│   ├── auth.js
│   ├── posts.js
│   └── users.js
├── views/               # EJSテンプレート
│   ├── auth/
│   ├── posts/
│   ├── users/
│   └── partials/
├── .env                 # 環境変数
├── package.json
└── server.js            # エントリーポイント

セットアップ

1. プロジェクトの初期化

mkdir sns-app
cd sns-app
npm init -y

2. 必要なパッケージのインストール

npm install express mongoose ejs express-session connect-mongo passport passport-local bcrypt dotenv express-flash express-validator method-override multer cloudinary
npm install --save-dev nodemon

3. package.jsonのスクリプト設定

{
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  }
}

主要な実装

1. サーバーのセットアップ (server.js)

require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoStore = require('connect-mongo');
const passport = require('passport');
const flash = require('express-flash');
const methodOverride = require('method-override');

const app = express();

// データベース接続
require('./config/database');

// Passport設定
require('./config/passport')(passport);

// ミドルウェア設定
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(methodOverride('_method'));
app.use(express.static('public'));

// ビューエンジン設定
app.set('view engine', 'ejs');

// セッション設定
app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    store: MongoStore.create({
      mongoUrl: process.env.MONGODB_URI
    })
  })
);

// Passport初期化
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());

// ルート設定
app.use('/', require('./routes/auth'));
app.use('/posts', require('./routes/posts'));
app.use('/users', require('./routes/users'));

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

2. ユーザーモデル (models/User.js)

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true,
    trim: true
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true
  },
  password: {
    type: String,
    required: true
  },
  displayName: String,
  bio: String,
  profileImage: {
    type: String,
    default: '/images/default-avatar.svg'
  },
  followers: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  }],
  following: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  }]
}, {
  timestamps: true
});

// パスワードをハッシュ化
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

module.exports = mongoose.model('User', userSchema);

3. 投稿モデル (models/Post.js)

const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
  content: {
    type: String,
    required: true,
    maxlength: 500
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  image: String,
  likes: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  }],
  comments: [{
    user: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    },
    content: String,
    createdAt: {
      type: Date,
      default: Date.now
    }
  }]
}, {
  timestamps: true
});

module.exports = mongoose.model('Post', postSchema);

4. Passport認証設定 (config/passport.js)

const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
const User = require('../models/User');

module.exports = function(passport) {
  passport.use(
    new LocalStrategy(
      { usernameField: 'email' },
      async (email, password, done) => {
        try {
          const user = await User.findOne({ email: email.toLowerCase() });
          
          if (!user) {
            return done(null, false, { message: 'メールアドレスまたはパスワードが正しくありません' });
          }

          const isMatch = await bcrypt.compare(password, user.password);
          
          if (!isMatch) {
            return done(null, false, { message: 'メールアドレスまたはパスワードが正しくありません' });
          }

          return done(null, user);
        } catch (error) {
          return done(error);
        }
      }
    )
  );

  passport.serializeUser((user, done) => {
    done(null, user.id);
  });

  passport.deserializeUser(async (id, done) => {
    try {
      const user = await User.findById(id);
      done(null, user);
    } catch (error) {
      done(error);
    }
  });
};

5. 認証ミドルウェア (middleware/auth.js)

exports.ensureAuthenticated = (req, res, next) => {
  if (req.isAuthenticated()) {
    return next();
  }
  req.flash('error', 'ログインが必要です');
  res.redirect('/login');
};

exports.ensureGuest = (req, res, next) => {
  if (!req.isAuthenticated()) {
    return next();
  }
  res.redirect('/posts');
};

主要機能の実装

いいね機能

// controllers/postController.js
exports.likePost = async (req, res) => {
  try {
    const post = await Post.findById(req.params.id);
    const likeIndex = post.likes.indexOf(req.user._id);

    if (likeIndex === -1) {
      post.likes.push(req.user._id);
    } else {
      post.likes.splice(likeIndex, 1);
    }

    await post.save();

    res.json({ 
      success: true, 
      likeCount: post.likes.length,
      isLiked: likeIndex === -1
    });
  } catch (error) {
    res.status(500).json({ error: 'エラーが発生しました' });
  }
};

フォロー機能

// controllers/userController.js
exports.followUser = async (req, res) => {
  try {
    const userToFollow = await User.findById(req.params.id);
    const currentUser = await User.findById(req.user._id);

    const isFollowing = currentUser.following.includes(userToFollow._id);

    if (isFollowing) {
      currentUser.following.pull(userToFollow._id);
      userToFollow.followers.pull(currentUser._id);
    } else {
      currentUser.following.push(userToFollow._id);
      userToFollow.followers.push(currentUser._id);
    }

    await currentUser.save();
    await userToFollow.save();

    res.json({
      success: true,
      isFollowing: !isFollowing,
      followerCount: userToFollow.followers.length
    });
  } catch (error) {
    res.status(500).json({ error: 'エラーが発生しました' });
  }
};

フロントエンド

EJSテンプレート例

<!-- views/posts/timeline.ejs -->
<%- include('../partials/header', { title: 'タイムライン' }) %>

<div class="timeline-container">
  <% posts.forEach(function(post) { %>
    <div class="post-card">
      <div class="post-header">
        <a href="/users/<%= post.author._id %>">
          <img src="<%= post.author.profileImage %>" alt="<%= post.author.username %>">
          <span><%= post.author.displayName %></span>
        </a>
      </div>
      
      <div class="post-content">
        <p><%= post.content %></p>
        <% if (post.image) { %>
          <img src="<%= post.image %>" alt="投稿画像">
        <% } %>
      </div>
      
      <div class="post-actions">
        <button class="like-btn" data-post-id="<%= post._id %>">
          <i class="fas fa-heart"></i>
          <span><%= post.likes.length %></span>
        </button>
      </div>
    </div>
  <% }); %>
</div>

<%- include('../partials/footer') %>

JavaScript (いいね機能)

// public/js/main.js
document.querySelectorAll('.like-btn').forEach(button => {
  button.addEventListener('click', async function() {
    const postId = this.dataset.postId;
    
    try {
      const response = await fetch(`/posts/${postId}/like`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
      });
      
      const data = await response.json();
      
      if (data.success) {
        const icon = this.querySelector('i');
        const count = this.querySelector('span');
        
        icon.classList.toggle('fas');
        icon.classList.toggle('far');
        count.textContent = data.likeCount;
      }
    } catch (error) {
      console.error('Error:', error);
    }
  });
});

環境変数の設定

.env
MONGODB_URI=mongodb://localhost:27017/sns-app
SESSION_SECRET=your-secret-key-here
PORT=3000
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret

起動方法

# MongoDBを起動
# Windows: net start MongoDB
# macOS/Linux: sudo systemctl start mongod

# アプリケーションを起動
npm run dev

# ブラウザでアクセス
# http://localhost:3000

セキュリティ対策

  1. パスワードのハッシュ化: bcryptを使用
  2. セッション管理: MongoDBにセッションを保存
  3. CSRF対策: method-overrideを使用
  4. バリデーション: express-validatorで入力検証
  5. 環境変数: 機密情報は.envファイルで管理

今後の拡張案

  • リアルタイム通知機能(Socket.io)
  • ダイレクトメッセージ機能
  • ハッシュタグ機能
  • トレンド表示
  • メール認証
  • パスワードリセット機能
  • 二段階認証
  • ダークモード

まとめ

この記事では、Node.js + Express + MongoDB を使ってSNS風Webアプリを作成しました。
初心者でも10分でここまでできるとは、正直自分でも驚きです 😳

主なポイントは以下の通りです:

  • MVCアーキテクチャで構造化されたコード
  • Passport.js による安全な認証機能
  • MongoDB でのユーザー・投稿リレーション管理
  • EJSテンプレートエンジンで簡単にビューを構築
  • RESTful API設計で拡張もしやすい

完全なソースコードはGitHubリポジトリで公開中ですので、ぜひ触ってみてください!

参考リンク

おわりに

ちなみに、この記事もほぼBobに書いてもらいました笑😆

  • 作成時間はたったの10分
  • エラーもなし
  • 私はVSCodeでBobの指示を眺めていただけ

初心者でも「AIと一緒に作る」だけで、ここまで本格的なアプリが作れるのは衝撃的です。

次はどんなアプリを作ろうかなあ。
みなさんもぜひBobを使って、自分だけのアプリを作ってみてください!

質問・感想・作ってみた報告などのコメント、たくさんお待ちしております!💡
Happy Coding! 🚀

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?