<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Scalia Abel</title>
    <description>The latest articles on DEV Community by Scalia Abel (@scaabel).</description>
    <link>https://dev.to/scaabel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F810226%2F7673c013-2ac4-4e71-96da-862785540afb.jpeg</url>
      <title>DEV Community: Scalia Abel</title>
      <link>https://dev.to/scaabel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scaabel"/>
    <language>en</language>
    <item>
      <title>Containerizing A NextJs Application For Development</title>
      <dc:creator>Scalia Abel</dc:creator>
      <pubDate>Fri, 14 Apr 2023 16:21:04 +0000</pubDate>
      <link>https://dev.to/scaabel/containerizing-a-nextjs-application-for-development-204d</link>
      <guid>https://dev.to/scaabel/containerizing-a-nextjs-application-for-development-204d</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Containerizing your NextJs application can be an efficient way of developing it, as it provides a more isolated and reproducible environment. In this article, we will go through the steps required to containerize a NextJs application and explain each step in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we begin, you should have a basic knowledge of NextJs and familiarity with Docker CLI and Docker Compose commands.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The docker image we'll be building should not be used in a production environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creating A NextJs Application
&lt;/h2&gt;

&lt;p&gt;The first step is to create a NextJs application. To do this, you can run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npx create-next-app docker-next


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will create a NextJs application called &lt;code&gt;docker-next&lt;/code&gt; in your current working directory. &lt;/p&gt;

&lt;p&gt;You will be prompt to decide whether to include &lt;code&gt;TypeScript&lt;/code&gt;, &lt;code&gt;TailwindCSS&lt;/code&gt; and a few other things. Feel free to choose whichever options as we won't be going through NextJs in depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Docker
&lt;/h2&gt;

&lt;p&gt;If you are on Windows or Mac, you can directly install &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;docker desktop&lt;/a&gt;. If you are on linux, you may want to follow the instructions listed &lt;a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have Docker installed, verify that the Docker CLI and Docker Compose are installed correctly by running the following commands:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker &lt;span class="nt"&gt;-v&lt;/span&gt;
docker-compose &lt;span class="nt"&gt;-v&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These commands should output the versions of Docker and Docker Compose, respectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Containerizing NextJs
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;We should delete our &lt;code&gt;node_modules&lt;/code&gt; folder and &lt;code&gt;package-lock.json&lt;/code&gt; file because it helps to reduce the size of the image and ensure that the dependencies in the image are consistent with the ones declared in the &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we have the necessary tools to run docker, we can start containerizing our NextJs application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating A Dockerfile
&lt;/h3&gt;

&lt;p&gt;The first step is to create a Dockerfile for our application. You can do this by running the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;touch &lt;/span&gt;Dockerfile


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command will create a new empty file named Dockerfile in your current directory.&lt;/p&gt;

&lt;p&gt;The contents of your Dockerfile should look like this.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

FROM node:18-alpine

WORKDIR /app

COPY package.json ./

RUN npm install

COPY . .

CMD ["npm", "run", "dev"]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's go through each line of this Dockerfile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;FROM node:18-alpine&lt;/strong&gt;: This line specifies the base image to use for our container. We're using the &lt;code&gt;node&lt;/code&gt; image with version &lt;code&gt;18&lt;/code&gt; that is built on top of the &lt;code&gt;alpine&lt;/code&gt; Linux distribution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WORKDIR /app&lt;/strong&gt;: This line sets the working directory for our container to &lt;code&gt;/app&lt;/code&gt;. This is important to avoid clashing of folder names between our application and the container's.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;COPY package.json ./&lt;/strong&gt;: This line copies the &lt;code&gt;package.json&lt;/code&gt; file from our local machine to the &lt;code&gt;/app&lt;/code&gt; directory inside the container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RUN npm install&lt;/strong&gt;: This line installs the &lt;code&gt;dependencies&lt;/code&gt; required by our application inside the container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;COPY . .&lt;/strong&gt;: This line copies the entire contents of our application from our local machine to the &lt;code&gt;/app&lt;/code&gt; directory inside the container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CMD ["npm", "run", "dev"]&lt;/strong&gt;: This line specifies the command to run when the container starts. In this case, we're running the &lt;code&gt;npm run dev&lt;/code&gt; command, which will start our NextJs development server.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Replace &lt;code&gt;npm&lt;/code&gt; with your choice of package manager.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Bulding The Docker Image
&lt;/h3&gt;

&lt;p&gt;Now that we have our Dockerfile, we can build the Docker image by running the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker build &lt;span class="nt"&gt;-t&lt;/span&gt; docker-next &lt;span class="nb"&gt;.&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command will build a new Docker image named &lt;code&gt;docker-next&lt;/code&gt; from the Dockerfile in our current directory. The &lt;code&gt;-t&lt;/code&gt; flag specifies the name of the image, and the &lt;code&gt;.&lt;/code&gt; specifies the build context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running The Container
&lt;/h3&gt;

&lt;p&gt;To run the container, we can use the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker run docker-next &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="nt"&gt;-v&lt;/span&gt; /app/node_modules &lt;span class="nt"&gt;-v&lt;/span&gt; .:/app


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here's what each option means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;-p 3000:3000&lt;/strong&gt;: maps port &lt;code&gt;3000&lt;/code&gt; of the container to port &lt;code&gt;3000&lt;/code&gt; of the host machine. This means that you can access the application by navigating to &lt;code&gt;http://localhost:3000&lt;/code&gt; in your web browser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;-v /app/node_modules&lt;/strong&gt;: mounts the &lt;code&gt;/app/node_modules&lt;/code&gt; directory in the container as a volume. This is useful because it allows you to take advantage of Docker's caching mechanism. Since &lt;code&gt;node_modules&lt;/code&gt; is typically a large directory that doesn't change very often, mounting it as a volume means that it won't have to be rebuilt every time you make changes to your application's code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;-v .:/app&lt;/strong&gt;: mounts the current directory (i.e., the directory where you run the command) as a volume at the &lt;code&gt;/app&lt;/code&gt; directory inside the container. This is where your Next.js application code lives.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So in summary, this command runs a Docker container from the &lt;code&gt;docker-next&lt;/code&gt; image, maps port &lt;code&gt;3000&lt;/code&gt; of the container to port &lt;code&gt;3000&lt;/code&gt; of the host machine, mounts the &lt;code&gt;/app/node_modules&lt;/code&gt; directory in the container as a volume, and mounts the current directory as a volume at &lt;code&gt;/app&lt;/code&gt; inside the container&lt;/p&gt;

&lt;p&gt;Once the container has run, visit &lt;code&gt;localhost:3000&lt;/code&gt; in your browser and you should be greeted with the NextJs default page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqnig9v757l39u4oiwic8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqnig9v757l39u4oiwic8.png" alt="Image description" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having to type this command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

docker run docker-next -p 3000:3000 -v /app/node_modules -v .:/app


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;every single time, will slowly chip away your sanity. That's where docker compose comes in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter Docker Compose
&lt;/h3&gt;

&lt;p&gt;Docker Compose is a tool for defining and running multi-container Docker applications. It allows you to describe the services that make up your application in a YAML file, and then start and stop those services using a single command. &lt;/p&gt;

&lt;p&gt;With Docker Compose, you can also scale your application's services, set up networking between containers, and more. Essentially, Docker Compose makes it easier to manage and deploy multi-container Docker applications.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating The Docker Compose File
&lt;/h4&gt;

&lt;p&gt;We can create the docker compose file by running:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

touch docker-compose.yml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Paste this into the &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

version: '3.5'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: docker-next
    ports:
      - '3000:3000'
    volumes:
      - .:/app
      - /app/node_modules


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;First we define a service called &lt;code&gt;app&lt;/code&gt;, that will be build using the Dockerfile located in our root directory and run in a container. The service exposes the container's port &lt;code&gt;3000&lt;/code&gt; to the host's port &lt;code&gt;3000&lt;/code&gt; and mounts two volumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;.:app&lt;/strong&gt;: This will mount the directory where the Docker Compose file is located as a volume in the container at the &lt;code&gt;/app&lt;/code&gt; directory. This is useful for development as it allows for live reloading of code changes made on the host machine to be reflected in the container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;/app/node_modules&lt;/strong&gt;: This mounts the &lt;code&gt;node_modules&lt;/code&gt; directory in the container as a separate volume. This is done to avoid overriding the dependencies installed by &lt;code&gt;npm&lt;/code&gt; during the image build process with the dependencies installed on the host machine.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To start our application, just run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

docker-compose up


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Congratulations, you just containerized a NextJs application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;There is one caveat developing with a containerized application, which is running &lt;code&gt;cli&lt;/code&gt; commands. Specifically, in our case &lt;code&gt;npm&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;If you need to add new &lt;code&gt;dependencies&lt;/code&gt;, you will need to run:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

docker-compose run --rm app npm install &amp;lt;package name&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;docker-compose run&lt;/code&gt; command allows you to run a one-time command against a service defined in your &lt;code&gt;docker-compose.yml&lt;/code&gt; file. The &lt;code&gt;--rm&lt;/code&gt; option specifies that the container created from the service should be removed after the command is executed. This option ensures that the container is cleaned up and does not consume resources on your system after it's no longer needed.&lt;/p&gt;

&lt;p&gt;In this case, &lt;code&gt;app&lt;/code&gt; is the name of the service defined in &lt;code&gt;docker-compose.yml&lt;/code&gt; that the command is being run against. &lt;/p&gt;

&lt;p&gt;To keep our sanity in check, we'll be building a script to help us run &lt;code&gt;docker-compose&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt; and &lt;code&gt;node&lt;/code&gt; commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating A Script To Run Docker Compose, NPM And Node Commands
&lt;/h3&gt;

&lt;p&gt;We will be building a script based on &lt;a href="https://laravel.com/docs/10.x/sail#introduction" rel="noopener noreferrer"&gt;laravel/sail&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;harbor&lt;/code&gt; and make it executable.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;touch &lt;/span&gt;harbor
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x harbor


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Paste the following into the &lt;code&gt;harbor&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;display_help &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Harbor"&lt;/span&gt;
  &lt;span class="nb"&gt;echo
  echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage:"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor COMMAND [options] [arguments]"&lt;/span&gt;
  &lt;span class="nb"&gt;echo
  echo&lt;/span&gt; &lt;span class="s2"&gt;"Unknown commands are passed to the docker-compose binary."&lt;/span&gt;
  &lt;span class="nb"&gt;echo
  echo&lt;/span&gt; &lt;span class="s2"&gt;"docker-compose Commands:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor up        Start the application"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor up -d     Start the application in the background"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor stop      Stop the application"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor down      Stop the application and remove related resources"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor restart   Restart the application"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor ps        Display the status of all containers"&lt;/span&gt;
  &lt;span class="nb"&gt;echo
  echo&lt;/span&gt; &lt;span class="s2"&gt;"Node Commands:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor node ...         Run a Node command"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor node --version"&lt;/span&gt;
  &lt;span class="nb"&gt;echo
  echo&lt;/span&gt; &lt;span class="s2"&gt;"Npm Commands:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor npm ...        Run a Npm command"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor npm test"&lt;/span&gt;
  &lt;span class="nb"&gt;echo
  echo&lt;/span&gt; &lt;span class="s2"&gt;"Customization:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  harbor build --no-cache       Rebuild all of the harbor containers"&lt;/span&gt;

  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"npm"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;shift &lt;/span&gt;1
    docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; app npm &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"node"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;shift &lt;/span&gt;1
    docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; app node &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"help"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"--help"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"-h"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;display_help
  &lt;span class="k"&gt;else
    &lt;/span&gt;docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker/dev/docker-compose.yml &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi
else
  &lt;/span&gt;display_help
&lt;span class="k"&gt;fi&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;display_help&lt;/code&gt; function is defined first, which prints out the available commands, options, and arguments to the user.&lt;/p&gt;

&lt;p&gt;The script then checks if there is at least one command-line argument &lt;code&gt;($# -gt 0)&lt;/code&gt;. If there is, it checks if the first argument is &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;node&lt;/code&gt;. If the argument is &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;node&lt;/code&gt;, it runs the corresponding command using &lt;code&gt;docker-compose run --rm app&lt;/code&gt;. If the argument is &lt;code&gt;help&lt;/code&gt;, &lt;code&gt;--help&lt;/code&gt;, or &lt;code&gt;-h&lt;/code&gt;, it prints out the help information using the &lt;code&gt;display_help&lt;/code&gt; function. Otherwise, it passes the command and any arguments to &lt;code&gt;docker-compose&lt;/code&gt; to execute.&lt;/p&gt;

&lt;p&gt;If there are no command-line arguments, the script also prints out the help information using the &lt;code&gt;display_help&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;help&lt;/code&gt; output will look like this.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff10beb1kfphr9xuiezio.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff10beb1kfphr9xuiezio.png" alt="Image description" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we are able to run &lt;code&gt;npm&lt;/code&gt; and &lt;code&gt;node&lt;/code&gt; commands by using the script that we just created. You can take it up a notch and include conditions for &lt;code&gt;yarn&lt;/code&gt; and &lt;code&gt;pnpm&lt;/code&gt; or put everything in an &lt;code&gt;npm&lt;/code&gt; package and publish it, since it's basically framework agnostic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Containerizing a Next.js application with Docker provides a more isolated and reproducible environment, making it an efficient way of developing and onboarding new developers. &lt;/p&gt;

&lt;p&gt;Hopefully, this article has helped provide some clarity and deepen your understanding regarding docker. If you have any feedback or comments, feel free to leave them in the comments section.&lt;/p&gt;

&lt;p&gt;If you enjoyed this article, consider &lt;a href="https://ko-fi.com/scaliaabel" rel="noopener noreferrer"&gt;tipping me&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Link to &lt;a href="https://github.com/scaabel/docker-next" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>docker</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Laravel Google Login with Socialite</title>
      <dc:creator>Scalia Abel</dc:creator>
      <pubDate>Tue, 08 Feb 2022 17:37:28 +0000</pubDate>
      <link>https://dev.to/scaabel/laravel-google-login-with-socialite-30il</link>
      <guid>https://dev.to/scaabel/laravel-google-login-with-socialite-30il</guid>
      <description>&lt;p&gt;Social login help simplifies registration and login for the end users. Socialite is a social login package provided by Laravel. This package allows us to seamlessly integrate social logins like Facebook, Twitter, Google and many more. The list of adapters that &lt;strong&gt;Socialite&lt;/strong&gt; provides can be found &lt;a href="https://socialiteproviders.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Google Cloud Platform&lt;/strong&gt; (GCP) account is required for this example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;First we need to scaffold our application with Laravel breeze. You can follow the steps provided &lt;a href="https://laravel.com/docs/8.x/starter-kits#laravel-breeze-installation" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once the scaffolding is done, we will need to install the socialite package into our application.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="n"&gt;composer&lt;/span&gt; &lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="n"&gt;laravel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;socialite&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Setting Up Migrations
&lt;/h3&gt;

&lt;p&gt;Let's create a table to store the user's socialite login.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;migration&lt;/span&gt; &lt;span class="nc"&gt;CreateSocialiteLoginsTable&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, setup the columns for our &lt;code&gt;socialite_logins&lt;/code&gt; table.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Migrations\Migration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Schema\Blueprint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Schema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateSocialiteLoginsTable&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'socialite_logins'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'provider_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'provider'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dropIfExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'socialite_logins'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;user_id&lt;/code&gt; column will be used to map the socialite logins to the users. The type of provider will be saved in the &lt;code&gt;provider&lt;/code&gt; column, i.e &lt;code&gt;google&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Considering that the users are not required to register, make sure to change the &lt;code&gt;password&lt;/code&gt; column in our &lt;code&gt;CreateUsersTable&lt;/code&gt; migration to nullable, .&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Models
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;SocialiteLogin&lt;/code&gt; model.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="nc"&gt;SocialiteLogin&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Since we are only implementing the Google authentication and a user can only have one Google account, the relationship between the &lt;code&gt;SocialiteLogin&lt;/code&gt; model and &lt;code&gt;User&lt;/code&gt; model is &lt;code&gt;One-to-One&lt;/code&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you feel like implementing another social login, you will need to change the relationship to &lt;code&gt;Many-to-One&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add the &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;provider&lt;/code&gt; and &lt;code&gt;provider_id&lt;/code&gt; field to the &lt;code&gt;$fillable&lt;/code&gt; property in the &lt;code&gt;SocialiteLogin&lt;/code&gt; model.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Factories\HasFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Relations\BelongsTo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SocialiteLogin&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HasFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'provider'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'provider_id'&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;BelongsTo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;belongsTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, add the &lt;code&gt;socialiteLogin&lt;/code&gt; relationship in the &lt;code&gt;User&lt;/code&gt; model.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Factories\HasFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Auth\User&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;Authenticatable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Sanctum\HasApiTokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Authenticatable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HasFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HasApiTokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * The attributes that are mass assignable.
     *
     * @var string[]
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'remember_token'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * The attributes that should be cast.
     *
     * @var array
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$casts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'email_verified_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'datetime'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// change this to HasMany&lt;/span&gt;
    &lt;span class="c1"&gt;// if you are implementing&lt;/span&gt;
    &lt;span class="c1"&gt;// more than one social login&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;socialiteLogin&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;BelongsTo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;belongsTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SocialiteLogin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Setting Up Google Cloud Platform
&lt;/h3&gt;

&lt;p&gt;Login to a Google Cloud Platform account and select a project or create one. Click on the sidebar and navigate to the &lt;code&gt;API and services -&amp;gt; OAuth Consent Screen&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Setting Up OAuth Consent Screen
&lt;/h4&gt;

&lt;p&gt;We will be greeted by this screen.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fheu8y9xxvbyxsyed4yjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fheu8y9xxvbyxsyed4yjj.png" alt="OAuth Consent"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we are authenticating external users, the user type that we'll be selecting is external.&lt;/p&gt;

&lt;p&gt;Once we have selected the type of user, we will be redirected to the OAuth Consent Screen page. We are only required to fill in the app information section and the developer contact information.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1n4jrr8qmpg72gu0483u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1n4jrr8qmpg72gu0483u.png" alt="App Information"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The developer contact information will be your personal/work email.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjp1fg5ggfczoyhzj4sg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjp1fg5ggfczoyhzj4sg9.png" alt="Developer Contact"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After setting up the app information, we will be setting up the scope of permission that is needed for our application. Click the &lt;code&gt;add or remove scopes&lt;/code&gt; and select &lt;code&gt;userinfo.email&lt;/code&gt; and &lt;code&gt;userinfo.profile&lt;/code&gt; scope.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntutcu0so5ikxssxvtnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntutcu0so5ikxssxvtnw.png" alt="Scoped Permission"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;save and continue&lt;/code&gt; and setup the test user with an existing gmail account.&lt;/p&gt;
&lt;h4&gt;
  
  
  Setting Up Credentials
&lt;/h4&gt;

&lt;p&gt;Proceed to the credentials tab located on the sidebar and create a new &lt;code&gt;OAuth Client ID&lt;/code&gt;. The application type we are using is &lt;code&gt;web application&lt;/code&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are developing with an SPA, you will need to setup the &lt;code&gt;Authorized JavaScript Origin&lt;/code&gt; section. The &lt;code&gt;http://localhost:3000&lt;/code&gt; url is sufficient for this field.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;Authorized Redirect URIs&lt;/code&gt; is where we are going to handle the authentication of the user based on their email and profile. Our url will be &lt;code&gt;http://127.0.0.1:8000/login/google/callback&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1ke1q30swluydlwn836.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1ke1q30swluydlwn836.png" alt="Credentials"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we have created the credentials, we will be given the &lt;code&gt;CLIENT_ID&lt;/code&gt; and &lt;code&gt;CLIENT_SECRET&lt;/code&gt; values.&lt;/p&gt;

&lt;p&gt;The credentials will be placed in &lt;code&gt;services.php&lt;/code&gt; config and  &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// .env&lt;/span&gt;
&lt;span class="no"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="no"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="no"&gt;GOOGLE_REDIRECT_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// callback url&lt;/span&gt;

&lt;span class="c1"&gt;// services.php&lt;/span&gt;
&lt;span class="s1"&gt;'google'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
   &lt;span class="s1"&gt;'client_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_CLIENT_ID'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="s1"&gt;'client_secret'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_CLIENT_SECRET'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="s1"&gt;'redirect'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_REDIRECT_URI'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// callback url&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Setting Up URL and Callback
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;SocialiteLoginController&lt;/code&gt; controller. This controller will be consisting of two functions, &lt;code&gt;redirectToProvider&lt;/code&gt; and &lt;code&gt;handleProviderCallback&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="nc"&gt;SocialiteLoginController&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Handling The Redirection and Callback
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;redirectToProvider&lt;/code&gt; will only redirect the user to the google authentication page via the &lt;strong&gt;Socialite Driver&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Moving on to the &lt;code&gt;handleProviderCallback&lt;/code&gt;. First we get the &lt;code&gt;user&lt;/code&gt; from the &lt;code&gt;Socialite&lt;/code&gt; driver. This user returns a variety of informations since the Google provider supports OAuth2.0.&lt;/p&gt;

&lt;p&gt;In our case, we only need the user's &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; of the provider. We will be using Laravel's &lt;code&gt;firstOrCreate&lt;/code&gt; method to identify if the user exists. If it does not, we will create a new user.&lt;/p&gt;

&lt;p&gt;We will be using the same method for our &lt;code&gt;SocialiteLogin&lt;/code&gt; model. We are doing this to ensure that there is no duplication for the socialite login since a user can only have one google account.&lt;/p&gt;

&lt;p&gt;We can now safely authenticate the &lt;code&gt;user&lt;/code&gt; by using Laravel's &lt;code&gt;auth&lt;/code&gt; helper and redirect them to the dashboard.&lt;/p&gt;

&lt;p&gt;The controller will look something like this.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Socialite\Facades\Socialite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SocialiteLoginController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;redirectToProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handleProviderCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$socialiteUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;firstOrCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$socialiteUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$socialiteUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;SocialiteLogin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;firstOrCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'provider'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'provider_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$socialiteUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dashboard'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Setting Up The Route
&lt;/h4&gt;

&lt;p&gt;Add the controller to the &lt;code&gt;web.php&lt;/code&gt; route file.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// web.php&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/login/google'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;SocialiteLoginController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'redirectToProvider'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/login/google/callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;SocialiteLoginController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'handleProviderCallback'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The callback url needs to be the same with what we have setup in our Google Cloud Platform.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Updating the UI
&lt;/h3&gt;

&lt;p&gt;We begin by starting our development server.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The UI is pretty simple compared to what we have done so far.&lt;/p&gt;

&lt;p&gt;Since we will be doing a redirection, using an &lt;code&gt;anchor&lt;/code&gt; tag makes more sense than using a &lt;code&gt;button&lt;/code&gt;. Paste the code below to the &lt;code&gt;login.blade.php&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure to change the url accordingly&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

// login.blade.php
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
  &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/login/google"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ml-3 inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300 disabled:opacity-25 transition ease-in-out duration-150"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   Login Google
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Click on the &lt;code&gt;Login Google&lt;/code&gt; button to proceed to the Google OAuth Screen. After selecting a google account, we will be redirected to the dashboard after being authenticated. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwax4qkamdp36qm5vwudu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwax4qkamdp36qm5vwudu.png" alt="Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Most of the providers that &lt;a href="https://laravel.com/docs/8.x/socialite" rel="noopener noreferrer"&gt;Socialite&lt;/a&gt; supports have the same implementation thus helping us reduce the boilerplate needed to be written and maintained. &lt;/p&gt;

&lt;p&gt;Let me know if there are any questions or things that I can improve on.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>webdev</category>
      <category>php</category>
    </item>
  </channel>
</rss>
