<?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: Andrew Eze</title>
    <description>The latest articles on DEV Community by Andrew Eze (@andychukse).</description>
    <link>https://dev.to/andychukse</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%2F317368%2F58a9d979-ee54-4431-9cf2-2dfc2d921863.jpg</url>
      <title>DEV Community: Andrew Eze</title>
      <link>https://dev.to/andychukse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andychukse"/>
    <language>en</language>
    <item>
      <title>How to Handle Authentication in Nuxt with a Separate Backend API</title>
      <dc:creator>Andrew Eze</dc:creator>
      <pubDate>Tue, 15 Jul 2025 13:55:28 +0000</pubDate>
      <link>https://dev.to/andychukse/how-to-handle-authentication-in-nuxt-with-a-separate-backend-api-3e53</link>
      <guid>https://dev.to/andychukse/how-to-handle-authentication-in-nuxt-with-a-separate-backend-api-3e53</guid>
      <description>&lt;p&gt;One of the major decisions to make when building a frontend with Vue or React (Next.js or Nuxt.js) is how to handle authentication. There are several auth packages that can help you handle authentication in Nuxt. However, If you already have a backend api that handle authentication, most of the packages seems like overkill, some even introduce unnecessary complications like requiring a different database to store auth data or using a hosted solution.&lt;br&gt;
Backend frameworks or languages like FastApi/Django (python), Laravel (PHP), Nest.js/Adonis.js (Node.js) have robust authentication systems. So, you just need your frontend to interface with that. &lt;/p&gt;

&lt;p&gt;Most times, when building a frontend app with authentication, you need a system or package to; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle request to the backend to retrieve and store JWT token. &lt;/li&gt;
&lt;li&gt;If you're using password less option (social login or OTP/One time link), to send a request to the backend to handle generation of token and authentication. 
So, in summary you need to handle authentication and to manage auth state/data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Steps to Handling and Managing Authentication&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send a request with credentials to the backend api to retrieve a JWT token, a refresh token and in some cases user data.&lt;/li&gt;
&lt;li&gt;Store the tokens in a secure cookie or session. &lt;/li&gt;
&lt;li&gt;Store non-sensitive user data in local storage.&lt;/li&gt;
&lt;li&gt;Handle retrieving user data initially and subsequently.&lt;/li&gt;
&lt;li&gt;Handle refreshing the token when it expires.&lt;/li&gt;
&lt;li&gt;Use a middleware to manage access to protected pages. The middleware checks if a valid token is present. You can also check for roles and permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Manage Authentication easily on Nuxt&lt;/strong&gt;&lt;br&gt;
The steps above largely applies. However you can utilize in-built functions like useCookie, useState and packages like pinia to handle storage and retrieval of tokens and user data.&lt;/p&gt;

&lt;p&gt;You can use a package to handle to whole process. I created a package that handles secure JWT authentication and Google OAuth with flexible callback handling. It also handles Token Refresh, Route Protection, Auto imports, and SSR Support.&lt;br&gt;
You can use this package together with you backend api, and let your backend handle the authentication process to avoid duplication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To use the package:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;@andychukse/nuxt-auth&lt;/code&gt; dependency to your project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Using yarn
yarn add @andychukse/nuxt-auth

# Using npm
npm install @andychukse/nuxt-auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;@andychukse/nuxt-auth&lt;/code&gt; to the modules section of &lt;code&gt;nuxt.config.ts&lt;/code&gt; and add your config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default defineNuxtConfig({
  modules: ['@andychukse/nuxt-auth'],

  // Configure the auth module
  auth: {
    isEnabled: true,
    baseUrl: 'https://api_url/api/auth',
    callback: '/',
    endpoints: {
      signIn: { path: '/login', method: 'post' },
      signOut: { path: '/logout', method: 'post' },
      signUp: { path: '/register', method: 'post' },
      getSession: { path: '/session', method: 'get' },
      refresh: { path: '/refresh', method: 'post' },
      google: { path: '/google', method: 'post' },
    },
    token: {
      tokenPointer: '/access_token',
      refreshTokenPointer: '/refresh_token',
      type: 'Bearer',
      cookieName: 'auth.token',
      headerName: 'Authorization',
      maxAgeInSeconds: 86400, // 1 day
      sameSiteAttribute: 'lax', //or strict
      cookieDomain: '',
      secureCookieAttribute: false,
      httpOnlyCookieAttribute: false,
      refresh: {
        refreshOnlyToken: true,
        cookieName: 'auth.refresh',
        maxAgeInSeconds: 7776000, // 90 days
        requestTokenPointer: '/refresh_token',
      },
    },
    social: {
      google: {
        clientId: 'your-google-client-id.googleusercontent.com',
        redirectUri: 'http://localhost:3000/auth/google/callback',
        scopes: 'openid profile email'
      }
    },
  },
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tokenPointer indicates how your backend returns the token&lt;br&gt;
if it is &lt;code&gt;/access_token&lt;/code&gt;&lt;br&gt;
Then your backend is sending it back as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"access_token": "token string"
....
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can handle registration and login easily&lt;br&gt;
Simple Registration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class="auth-form"&amp;gt;
    &amp;lt;h2&amp;gt;Create Account&amp;lt;/h2&amp;gt;
    &amp;lt;form @submit.prevent="handleRegister"&amp;gt;
      &amp;lt;div class="form-group"&amp;gt;
        &amp;lt;label for="name"&amp;gt;Full Name:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          id="name"
          v-model="form.name" 
          type="text" 
          required 
          placeholder="Enter your full name"
        /&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div class="form-group"&amp;gt;
        &amp;lt;label for="email"&amp;gt;Email:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          id="email"
          v-model="form.email" 
          type="email" 
          required 
          placeholder="Enter your email"
        /&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div class="form-group"&amp;gt;
        &amp;lt;label for="password"&amp;gt;Password:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          id="password"
          v-model="form.password" 
          type="password" 
          required 
          placeholder="Choose a password"
          minlength="8"
        /&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div class="form-group"&amp;gt;
        &amp;lt;label for="confirmPassword"&amp;gt;Confirm Password:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          id="confirmPassword"
          v-model="form.confirmPassword" 
          type="password" 
          required 
          placeholder="Confirm your password"
        /&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;button type="submit" :disabled="loading || !isFormValid" class="btn-submit"&amp;gt;
        {{ loading ? 'Creating account...' : 'Create Account' }}
      &amp;lt;/button&amp;gt;

      &amp;lt;p v-if="error" class="error"&amp;gt;{{ error }}&amp;lt;/p&amp;gt;
    &amp;lt;/form&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
const { signUp, loading } = useAuth()

const form = reactive({
  name: '',
  email: '',
  password: '',
  confirmPassword: ''
})

const error = ref('')

const isFormValid = computed(() =&amp;gt; {
  return form.password === form.confirmPassword &amp;amp;&amp;amp; 
         form.password.length &amp;gt;= 8
})

const handleRegister = async () =&amp;gt; {
  try {
    error.value = ''

    if (!isFormValid.value) {
      error.value = 'Please check your password'
      return
    }

    await signUp({
      name: form.name,
      email: form.email,
      password: form.password
    }, {
      callbackUrl: '/welcome' // Optional: redirect after registration
    })
    // User will be redirected automatically
  } catch (err) {
    error.value = err.message || 'Registration failed'
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Route Protection with Middleware&lt;/strong&gt;&lt;br&gt;
Protect routes by adding the auth middleware. You can also extend the middleware by creating your own. This can be useful if you want to handle admin routes. You can check the user data for roles/permissions and grant or deny access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
definePageMeta({
  middleware: 'auth'
})
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;Protected Page&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;This page is only accessible to authenticated users.&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;useAuth()&lt;/code&gt; composable provides the authentication functionality&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
&amp;lt;div&amp;gt;
  &amp;lt;div v-if="token"&amp;gt;
   Authenticated
   {{ data?.name }}
  &amp;lt;/div&amp;gt;
  &amp;lt;div v-else&amp;gt;
   Not Authenticated
  &amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script setup&amp;gt;
const { data, token } = useAuth

//Update user
const updateUser = async () =&amp;gt; {
  //handle update user data
  //After update, You can refresh the user data with
  await getSession()
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read more on the documentation &lt;a href="https://andychukse.github.io/nuxt-auth" rel="noopener noreferrer"&gt;&lt;strong&gt;Simple Nuxt Auth&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>javascript</category>
      <category>api</category>
      <category>vue</category>
    </item>
    <item>
      <title>I just created a post on easy way handle authentication in Nuxt with a Backend</title>
      <dc:creator>Andrew Eze</dc:creator>
      <pubDate>Thu, 03 Jul 2025 15:29:54 +0000</pubDate>
      <link>https://dev.to/andychukse/i-just-created-a-post-on-easy-way-handle-authentication-in-nuxt-with-a-backend-5g39</link>
      <guid>https://dev.to/andychukse/i-just-created-a-post-on-easy-way-handle-authentication-in-nuxt-with-a-backend-5g39</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/andychukse/best-way-to-handle-nuxt-auth-with-backend-api-4c63" class="crayons-story__hidden-navigation-link"&gt;Best Way to Handle Nuxt Auth with Backend API&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/andychukse" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F317368%2F58a9d979-ee54-4431-9cf2-2dfc2d921863.jpg" alt="andychukse profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/andychukse" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Andrew Eze
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Andrew Eze
                
              
              &lt;div id="story-author-preview-content-2651991" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/andychukse" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F317368%2F58a9d979-ee54-4431-9cf2-2dfc2d921863.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Andrew Eze&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/andychukse/best-way-to-handle-nuxt-auth-with-backend-api-4c63" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 3 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/andychukse/best-way-to-handle-nuxt-auth-with-backend-api-4c63" id="article-link-2651991"&gt;
          Best Way to Handle Nuxt Auth with Backend API
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nuxt"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nuxt&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/backend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;backend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/api"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;api&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/andychukse/best-way-to-handle-nuxt-auth-with-backend-api-4c63#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>nuxt</category>
      <category>backend</category>
      <category>api</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Best Way to Handle Nuxt Auth with Backend API</title>
      <dc:creator>Andrew Eze</dc:creator>
      <pubDate>Thu, 03 Jul 2025 15:28:23 +0000</pubDate>
      <link>https://dev.to/andychukse/best-way-to-handle-nuxt-auth-with-backend-api-4c63</link>
      <guid>https://dev.to/andychukse/best-way-to-handle-nuxt-auth-with-backend-api-4c63</guid>
      <description>&lt;p&gt;One of the major decisions to make when building a frontend with Vue or React (Next.js or Nuxt.js) is how to handle authentication. There are several auth packages that can help you handle authentication in Nuxt. However, If you already have a backend api that handle authentication, most of the packages seems like overkill, some even introduce unnecessary complications like requiring a different database to store auth data or using a hosted solution.&lt;br&gt;
Backend frameworks or languages like FastApi/Django (python), Laravel (PHP), Nest.js/Adonis.js (Node.js) have robust authentication systems. So, you just need your frontend to interface with that. &lt;/p&gt;

&lt;p&gt;Most times, when building a frontend app with authentication, you need a system or package to; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle request to the backend to retrieve and store JWT token. &lt;/li&gt;
&lt;li&gt;If you're using password less option (social login or OTP/One time link), to send a request to the backend to handle generation of token and authentication. 
So, in summary you need to handle authentication and to manage auth state/data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Steps to Handling and Managing Authentication&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send a request with credentials to the backend api to retrieve a JWT token, a refresh token and in some cases user data.&lt;/li&gt;
&lt;li&gt;Store the tokens in a secure cookie or session. &lt;/li&gt;
&lt;li&gt;Store non-sensitive user data in local storage.&lt;/li&gt;
&lt;li&gt;Handle retrieving user data initially and subsequently.&lt;/li&gt;
&lt;li&gt;Handle refreshing the token when it expires.&lt;/li&gt;
&lt;li&gt;Use a middleware to manage access to protected pages. The middleware checks if a valid token is present. You can also check for roles and permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Manage Authentication easily on Nuxt&lt;/strong&gt;&lt;br&gt;
The steps above largely applies. However you can utilize in-built functions like useCookie, useState and packages like pinia to handle storage and retrieval of tokens and user data.&lt;/p&gt;

&lt;p&gt;You can use a package to handle to whole process. I created a package that handles secure JWT authentication and Google OAuth with flexible callback handling. It also handles Token Refresh, Route Protection, Auto imports, and SSR Support.&lt;br&gt;
You can use this package together with you backend api, and let your backend handle the authentication process to avoid duplication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To use the package:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;@andychukse/nuxt-auth&lt;/code&gt; dependency to your project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Using yarn
yarn add @andychukse/nuxt-auth

# Using npm
npm install @andychukse/nuxt-auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;@andychukse/nuxt-auth&lt;/code&gt; to the modules section of &lt;code&gt;nuxt.config.ts&lt;/code&gt; and add your config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default defineNuxtConfig({
  modules: ['@andychukse/nuxt-auth'],

  // Configure the auth module
  auth: {
    isEnabled: true,
    baseUrl: 'https://api_url/api/auth',
    callback: '/',
    endpoints: {
      signIn: { path: '/login', method: 'post' },
      signOut: { path: '/logout', method: 'post' },
      signUp: { path: '/register', method: 'post' },
      getSession: { path: '/session', method: 'get' },
      refresh: { path: '/refresh', method: 'post' },
      google: { path: '/google', method: 'post' },
    },
    token: {
      tokenPointer: '/access_token',
      refreshTokenPointer: '/refresh_token',
      type: 'Bearer',
      cookieName: 'auth.token',
      headerName: 'Authorization',
      maxAgeInSeconds: 86400, // 1 day
      sameSiteAttribute: 'lax', //or strict
      cookieDomain: '',
      secureCookieAttribute: false,
      httpOnlyCookieAttribute: false,
      refresh: {
        refreshOnlyToken: true,
        cookieName: 'auth.refresh',
        maxAgeInSeconds: 7776000, // 90 days
        requestTokenPointer: '/refresh_token',
      },
    },
    social: {
      google: {
        clientId: 'your-google-client-id.googleusercontent.com',
        redirectUri: 'http://localhost:3000/auth/google/callback',
        scopes: 'openid profile email'
      }
    },
  },
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tokenPointer indicates how your backend returns the token&lt;br&gt;
if it is &lt;code&gt;/access_token&lt;/code&gt;&lt;br&gt;
Then your backend is sending it back as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"access_token": "token string"
....
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can handle registration and login easily&lt;br&gt;
Simple Registration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class="auth-form"&amp;gt;
    &amp;lt;h2&amp;gt;Create Account&amp;lt;/h2&amp;gt;
    &amp;lt;form @submit.prevent="handleRegister"&amp;gt;
      &amp;lt;div class="form-group"&amp;gt;
        &amp;lt;label for="name"&amp;gt;Full Name:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          id="name"
          v-model="form.name" 
          type="text" 
          required 
          placeholder="Enter your full name"
        /&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div class="form-group"&amp;gt;
        &amp;lt;label for="email"&amp;gt;Email:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          id="email"
          v-model="form.email" 
          type="email" 
          required 
          placeholder="Enter your email"
        /&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div class="form-group"&amp;gt;
        &amp;lt;label for="password"&amp;gt;Password:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          id="password"
          v-model="form.password" 
          type="password" 
          required 
          placeholder="Choose a password"
          minlength="8"
        /&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div class="form-group"&amp;gt;
        &amp;lt;label for="confirmPassword"&amp;gt;Confirm Password:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          id="confirmPassword"
          v-model="form.confirmPassword" 
          type="password" 
          required 
          placeholder="Confirm your password"
        /&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;button type="submit" :disabled="loading || !isFormValid" class="btn-submit"&amp;gt;
        {{ loading ? 'Creating account...' : 'Create Account' }}
      &amp;lt;/button&amp;gt;

      &amp;lt;p v-if="error" class="error"&amp;gt;{{ error }}&amp;lt;/p&amp;gt;
    &amp;lt;/form&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
const { signUp, loading } = useAuth()

const form = reactive({
  name: '',
  email: '',
  password: '',
  confirmPassword: ''
})

const error = ref('')

const isFormValid = computed(() =&amp;gt; {
  return form.password === form.confirmPassword &amp;amp;&amp;amp; 
         form.password.length &amp;gt;= 8
})

const handleRegister = async () =&amp;gt; {
  try {
    error.value = ''

    if (!isFormValid.value) {
      error.value = 'Please check your password'
      return
    }

    await signUp({
      name: form.name,
      email: form.email,
      password: form.password
    }, {
      callbackUrl: '/welcome' // Optional: redirect after registration
    })
    // User will be redirected automatically
  } catch (err) {
    error.value = err.message || 'Registration failed'
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Route Protection with Middleware&lt;/strong&gt;&lt;br&gt;
Protect routes by adding the auth middleware. You can also extend the middleware by creating your own. This can be useful if you want to handle admin routes. You can check the user data for roles/permissions and grant or deny access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
definePageMeta({
  middleware: 'auth'
})
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;Protected Page&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;This page is only accessible to authenticated users.&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;useAuth()&lt;/code&gt; composable provides the authentication functionality&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
&amp;lt;div&amp;gt;
  &amp;lt;div v-if="token"&amp;gt;
   Authenticated
   {{ data?.name }}
  &amp;lt;/div&amp;gt;
  &amp;lt;div v-else&amp;gt;
   Not Authenticated
  &amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script setup&amp;gt;
const { data, token } = useAuth

//Update user
const updateUser = async () =&amp;gt; {
  //handle update user data
  //After update, You can refresh the user data with
  await getSession()
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read more on the documentation &lt;a href="https://andychukse.github.io/nuxt-auth" rel="noopener noreferrer"&gt;&lt;strong&gt;Simple Nuxt Auth&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>backend</category>
      <category>api</category>
      <category>frontend</category>
    </item>
    <item>
      <title>How to Create OTP Input with Resend CountDown Timer in Livewire using Alphinejs and TailwindCss</title>
      <dc:creator>Andrew Eze</dc:creator>
      <pubDate>Tue, 02 Apr 2024 13:27:58 +0000</pubDate>
      <link>https://dev.to/andychukse/how-to-create-otp-input-with-resend-countdown-timer-in-livewire-using-alphinejs-and-tailwindcss-4d9i</link>
      <guid>https://dev.to/andychukse/how-to-create-otp-input-with-resend-countdown-timer-in-livewire-using-alphinejs-and-tailwindcss-4d9i</guid>
      <description>&lt;p&gt;If you're working with Laravel Livewire and need to create an OTP Input. Here is how to create a OTP input with a count down timer that has a resend button using Alphine.js and TailwindCss.&lt;/p&gt;

&lt;h4&gt;
  
  
  Design the Input Field
&lt;/h4&gt;

&lt;p&gt;The code below creates a single input field for entering a single number. The number of fields depends on the length of the OTP code. The next part will show you how to display the number of input as required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form class="fi-form grid gap-y-6"&amp;gt;
&amp;lt;input
type="tel"
maxlength="1"
class="border border-gray-500 w-10 h-10 text-center"
/&amp;gt;
@error('code')
  &amp;lt;p data-validation-error="" class="fi-fo-field-wrp-error-message text-sm text-danger-600 dark:text-danger-400"&amp;gt;
   {{ $message }}
  &amp;lt;/p&amp;gt;
@enderror
&amp;lt;button 
    style="--c-400:var(--primary-400);--c-500:var(--primary-500);--c-600:var(--primary-600);"
    class="fi-btn relative grid-flow-col items-center justify-center font-semibold outline-none transition duration-75 focus-visible:ring-2 rounded-lg fi-color-custom fi-btn-color-primary fi-size-md fi-btn-size-md gap-1.5 px-3 py-2 text-sm inline-grid shadow-sm bg-custom-600 text-white hover:bg-custom-500 focus-visible:ring-custom-500/50 dark:bg-custom-500 dark:hover:bg-custom-400 dark:focus-visible:ring-custom-400/50 fi-ac-action fi-ac-btn-action"
   type="submit"
&amp;gt;Verify&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Display Specific Number of Input fields
&lt;/h4&gt;

&lt;p&gt;Here we use javascript (alphinejs) to specify number of boxes to display and use for loop to display them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form 
  class="fi-form grid gap-y-6"
  wire:submit="verify"
  x-data="otpForm()"
&amp;gt;
    &amp;lt;div style="--cols-default: repeat(1, minmax(0, 1fr));" class="grid grid-cols-[--cols-default] fi-fo-component-ctn gap-6"&amp;gt;
        &amp;lt;div style="--col-span-default: span 1 / span 1;" class="col-[--col-span-default]"&amp;gt;
            &amp;lt;div data-field-wrapper="" class="fi-fo-field-wrp"&amp;gt;

            &amp;lt;div class="grid gap-y-2"&amp;gt;
                &amp;lt;input 
                    type="hidden" 
                    id="otp" 
                    wire:model="code"
                    required="required"
                    x-model="otp"
                &amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="grid gap-y-2"&amp;gt;
                &amp;lt;div class=""&amp;gt;
                    &amp;lt;div class="py-6 px-0 w-80 mx-auto text-center my-6"&amp;gt;
                        &amp;lt;div class="flex justify-between"&amp;gt;
                            &amp;lt;template x-for="(input, index) in length" :key="index"&amp;gt;
                                &amp;lt;input
                                    type="tel"
                                    maxlength="1"
                                    class="border border-gray-500 w-10 h-10 text-center"
                                    :x-ref="index"
                                    x-on:input="handleInput($event)"
                                    x-on:paste="handlePaste($event)"
                                    x-on:keyup="handleDelete($event)"

                                /&amp;gt;
                            &amp;lt;/template&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;

                @error('code')
                &amp;lt;p data-validation-error="" class="fi-fo-field-wrp-error-message text-sm text-danger-600 dark:text-danger-400"&amp;gt;
                {{ $message }}
                &amp;lt;/p&amp;gt;
                @enderror

            &amp;lt;/div&amp;gt;
            &amp;lt;div class="grid gap-y-2"&amp;gt;
                &amp;lt;div class="fi-form-actions"&amp;gt;
                    &amp;lt;div class="fi-ac gap-3 grid grid-cols-[repeat(auto-fit,minmax(0,1fr))]"&amp;gt;
                        &amp;lt;button 
                            style="--c-400:var(--primary-400);--c-500:var(--primary-500);--c-600:var(--primary-600);"
                            class="fi-btn relative grid-flow-col items-center justify-center font-semibold outline-none transition duration-75 focus-visible:ring-2 rounded-lg fi-color-custom fi-btn-color-primary fi-size-md fi-btn-size-md gap-1.5 px-3 py-2 text-sm inline-grid shadow-sm bg-custom-600 text-white hover:bg-custom-500 focus-visible:ring-custom-500/50 dark:bg-custom-500 dark:hover:bg-custom-400 dark:focus-visible:ring-custom-400/50 fi-ac-action fi-ac-btn-action"
                            type="submit"
                            x-on:click="$wire.code=document.getElementById('otp').value"
                        &amp;gt;
                            Verify
                        &amp;lt;/button&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

    &amp;lt;/div&amp;gt; 
    &amp;lt;/div&amp;gt;  
    &amp;lt;/form&amp;gt;

    @push('scripts')
    &amp;lt;script&amp;gt;
        function otpForm() {
            return {
                    length: 7,
                    otp: '',

                    handleInput(e) {

                        const input = e.target;

                        this.otp = Array.from(Array(this.length), (element, i) =&amp;gt; {
                            let ref = document.querySelector('[x-ref="' + i + '"]');
                            return ref.value || '';
                        }).join('');

                        if (input.nextElementSibling &amp;amp;&amp;amp; input.value) {
                            input.nextElementSibling.focus();
                            input.nextElementSibling.select();
                        }
                    },

                    handlePaste(e) {
                        const paste = e.clipboardData.getData('text');
                        this.otp = paste;

                        const inputs = Array.from(Array(this.length));

                        inputs.forEach((element, i) =&amp;gt; {
                            let ref = document.querySelector('[x-ref="' + i + '"]');
                            ref.value = paste[i] || '';
                        });
                    },

                    handleDelete(e) {
                        let key = e.keyCode || e.charCode;
                        if(key == 8 || key == 46) {
                            currentRef = e.target.getAttribute('x-ref');
                            const previous = parseInt(currentRef) - 1;

                            let ref = document.querySelector('[x-ref="' + previous + '"]');
                            ref &amp;amp;&amp;amp; ref.focus();
                        }
                    },
            }
        }
&amp;lt;/script&amp;gt;
@endpush
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We passed the javascript function to the form as &lt;code&gt;x-data&lt;/code&gt;. This will allow us access all the variables and functions inside the &lt;code&gt;otpForm()&lt;/code&gt; function inside the form element. &lt;br&gt;
The OtpForm function contains &lt;br&gt;
&lt;em&gt;the &lt;code&gt;length&lt;/code&gt; variable,&lt;/em&gt; that will show the number of boxes to display.&lt;br&gt;
&lt;em&gt;&lt;code&gt;handleInput&lt;/code&gt; function&lt;/em&gt; concatenates otp codes entered in all the boxes and stores it to the otp variable.&lt;br&gt;
&lt;em&gt;&lt;code&gt;handlePaste&lt;/code&gt; function&lt;/em&gt; helps to transfer copied otp content from clipboard to the boxes.&lt;br&gt;
&lt;em&gt;&lt;code&gt;handleDelete&lt;/code&gt; function&lt;/em&gt; helps handle deleting of contents of the otp boxes and refocuses the cursor.&lt;/p&gt;

&lt;p&gt;We used &lt;code&gt;:x-ref&lt;/code&gt; to uniquely identify each input box and then used &lt;code&gt;document.querySelector&lt;/code&gt; to retrieve the value of each of the boxes based on their position.&lt;/p&gt;

&lt;p&gt;We also added a hidden field to store the otp code before submitting to our model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input 
  type="hidden" 
  id="otp" 
  wire:model="code"
  required="required"
  x-model="otp"
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Add Resend Button with CountDown Timer
&lt;/h4&gt;

&lt;p&gt;Let's add a resend button with countdown timer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            &amp;lt;div class="grid gap-y-2 text-center" x-data="otpSend(80)" x-init="init()"&amp;gt;
            &amp;lt;template  x-if="getTime() &amp;lt;= 0"&amp;gt;
            &amp;lt;form wire:submit="resendOtp"&amp;gt;
                &amp;lt;button
                    type="submit"
                &amp;gt;
                    Resend OTP
                    &amp;lt;div wire:loading&amp;gt;
                            &amp;lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"&amp;gt;
                                &amp;lt;circle fill="#FF156D" stroke="#FF156D" stroke-width="15" r="15" cx="40" cy="100"&amp;gt;
                                    &amp;lt;animate attributeName="opacity" calcMode="spline" dur="2" values="1;0;1;" keySplines=".5 0 .5 1;.5 0 .5 1" repeatCount="indefinite" begin="-.4"&amp;gt;
                                    &amp;lt;/animate&amp;gt;
                                &amp;lt;/circle&amp;gt;
                                &amp;lt;circle fill="#FF156D" stroke="#FF156D" stroke-width="15" r="15" cx="100" cy="100"&amp;gt;
                                    &amp;lt;animate attributeName="opacity" calcMode="spline" dur="2" values="1;0;1;" keySplines=".5 0 .5 1;.5 0 .5 1" repeatCount="indefinite" begin="-.2"&amp;gt;
                                    &amp;lt;/animate&amp;gt;
                                &amp;lt;/circle&amp;gt;
                                    &amp;lt;circle fill="#FF156D" stroke="#FF156D" stroke-width="15" r="15" cx="160" cy="100"&amp;gt;
                                        &amp;lt;animate attributeName="opacity" calcMode="spline" dur="2" values="1;0;1;" keySplines=".5 0 .5 1;.5 0 .5 1" repeatCount="indefinite" begin="0"&amp;gt;
                                        &amp;lt;/animate&amp;gt;
                                &amp;lt;/circle&amp;gt;
                            &amp;lt;/svg&amp;gt;

                            &amp;lt;/div&amp;gt;
                &amp;lt;/button&amp;gt;
                &amp;lt;input type="hidden" wire:model="logid"&amp;gt;
            &amp;lt;/form&amp;gt;
            &amp;lt;/template&amp;gt;
            &amp;lt;template x-if="getTime() &amp;gt; 0"&amp;gt;
            &amp;lt;small&amp;gt;
                Resend OTP in 
                &amp;lt;span x-text="formatTime(getTime())"&amp;gt;&amp;lt;/span&amp;gt;
            &amp;lt;/small&amp;gt;
            &amp;lt;/template&amp;gt;
            &amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
function otpSend(num) {
            const milliseconds = num * 1000 //60 seconds
            const currentDate = Date.now() + milliseconds
            var countDownTime = new Date(currentDate).getTime()
            let interval;

            return {
                countDown: milliseconds,
                countDownTimer: new Date(currentDate).getTime(),
                intervalID: null,
                init(){
                    if (!this.intervalID ) {
                        this.intervalID = setInterval(() =&amp;gt; {
                            this.countDown = this.countDownTimer - new Date().getTime();
                        }, 1000);
                    }

                },
                getTime(){
                    if(this.countDown &amp;lt; 0){
                        this.clearTimer()
                    }
                    return this.countDown;
                },
                formatTime(num){

                    var date = new Date(num);
                    return new Date(this.countDown).toLocaleTimeString(navigator.language, {
                        minute: '2-digit',
                        second:'2-digit'
                    });

                },
                clearTimer() {
                    clearInterval(this.intervalID);
                }

            }
        }

        &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;otpSend(num)&lt;/code&gt; function uses a countdown timer to only display the resend button after specified time &lt;code&gt;num&lt;/code&gt; in seconds.&lt;/p&gt;

&lt;p&gt;You can see the full &lt;a href="https://github.com/andychukse/otp-input-livewire-alphinejs" rel="noopener noreferrer"&gt;source code on github here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>otp</category>
      <category>alphinejs</category>
      <category>tailwindcss</category>
      <category>livewire</category>
    </item>
    <item>
      <title>How to Run a Python Flask App in Docker with Gunicorn, Nginx, Redis, Celery and Crontab</title>
      <dc:creator>Andrew Eze</dc:creator>
      <pubDate>Thu, 10 Aug 2023 14:09:02 +0000</pubDate>
      <link>https://dev.to/andychukse/how-to-run-a-python-flask-app-in-docker-with-gunicorn-nginx-redis-celery-and-crontab-3m6c</link>
      <guid>https://dev.to/andychukse/how-to-run-a-python-flask-app-in-docker-with-gunicorn-nginx-redis-celery-and-crontab-3m6c</guid>
      <description>&lt;p&gt;If you have built a Python Flask application that has need for Redis and cron jobs and you're looking to host your application using Docker, this post will provide you with how to set up your app to run smoothly using Nginx as reverse webserver proxy and Gunicorn as app server.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post assumes you know how to build applications using Python Flask. Also, for this post, I assumed using a remote database server (MySQL)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can check my previous post on how &lt;a href="https://dev.to/andychukse/building-a-user-authentication-api-using-python-flask-and-mysql-4lki"&gt;Build a User Authentication API using Python Flask and MySQL&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenges with docker&lt;/strong&gt;&lt;br&gt;
There can only be one CMD instruction in a Dockerfile. Considering our application uses Celery and Redis to handle queue and also requires running cron jobs. Running a background process to keep running your jobs in a single Docker container can be tricky. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can use an &lt;code&gt;entrypoint.sh&lt;/code&gt; script&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3.12-rc-alpine
COPY app_process app_process
COPY bin/crontab /etc/cron.d/crontab 
RUN chmod +x /etc/cron.d/crontab
RUN crontab /etc/cron.d/crontab
COPY start.sh start.sh
CMD /start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start.sh  script could be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# turn on bash's job control
set -m

# Start the primary process and put it in the background
gunicorn --bind 0.0.0.0:5000 wsgi:app --log-level=debug --workers=2 &amp;amp;

# cron
cron -f &amp;amp;

#celery
celery -A myapp.celery worker --loglevel=INFO

# now we bring the primary process back into the foreground
# and leave it there
fg %1

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;You can chain multiple commands to start all services in a single&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
CMD gunicorn --bind 0.0.0.0:5000 wsgi:app --log-level=debug --workers=2 &amp;amp; cron -f &amp;amp; celery -A myapp.celery worker --loglevel=INFO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;You can also use &lt;code&gt;supervisord&lt;/code&gt; to manage the processes.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# syntax=docker/dockerfile:1
FROM python:3.12-rc-alpine
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y supervisor
RUN mkdir -p /var/log/supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY app_process app_process
COPY bin/crontab /etc/cron.d/crontab 
RUN chmod +x /etc/cron.d/crontab
RUN crontab /etc/cron.d/crontab
CMD ["/usr/bin/supervisord"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;supervisord&lt;/code&gt; config could be something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[supervisord]
nodaemon=true
user=root

[program:celeryworker]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=celery -A myapp.celery worker --loglevel=INFO
autostart=true
autorestart=true

[program:myapp_gunicorn]
command=gunicorn --bind 0.0.0.0:5000 wsgi:app --log-level=debug --workers=2
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:cron]
command = cron -f -L 15
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/cron.log
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue with any of the above approach is you will be responsible for monitoring them if any service fails and tries to recover. For example crontab could stop running while the main app is working, you have to handle how to recover the crontab without restarting the whole container.&lt;/p&gt;

&lt;p&gt;It’s best practice to separate areas of concern by using one service per container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Multiple Containers.
&lt;/h2&gt;

&lt;p&gt;You can use multiple containers to run the different services. In this solution I used &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one container for the Flask App,&lt;/li&gt;
&lt;li&gt;one container for the redis service&lt;/li&gt;
&lt;li&gt;one container for the cronjob and Celery (Queue service) using &lt;code&gt;Supervisord&lt;/code&gt; to manage celery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: &lt;em&gt;You decide to further move the celery (queue service) into a separate container if you want to.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The DockerFile for Flask App&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3.11.4-slim-bullseye

# set work directory
WORKDIR /app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

ARG UID=1000
ARG GID=1000

RUN apt-get update \
  &amp;amp;&amp;amp; apt-get install -y --no-install-recommends build-essential default-libmysqlclient-dev default-mysql-client curl libpq-dev pkg-config \
  &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \
  &amp;amp;&amp;amp; apt-get clean

# RUN useradd -m python
# RUN chown -R python:python /app

# USER python


# If you have a requirement.txt file
COPY requirements/main.txt requirements/main.txt

# install dependencies
RUN pip install --upgrade pip
RUN pip install -r requirements/main.txt

COPY . /app/

RUN pip install -e .


CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--worker-tmp-dir", "/dev/shm", "--workers", "2", "--worker-class", "gevent", "--worker-connections", "1000", "wsgi:app", "--log-level", "debug"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Docker File for Crontab and Celery&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3.11.4-slim-bullseye

# set work directory
WORKDIR /cronapp/


# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

ARG UID=1000
ARG GID=1000

RUN apt-get update \
  &amp;amp;&amp;amp; apt-get install -y --no-install-recommends supervisor build-essential default-libmysqlclient-dev default-mysql-client curl cron libpq-dev pkg-config \
  &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \
  &amp;amp;&amp;amp; apt-get clean

# RUN useradd -m python
# RUN chown -R python:python /app
# USER python


COPY requirements/main.txt requirements/main.txt

# install dependencies
RUN pip install --upgrade pip
RUN pip install -r requirements/main.txt

COPY . /cronapp/

RUN pip install -e .

# Setup cronjob
RUN touch /var/log/cron.log 

# Copying the crontab file 
COPY cron/bin/crontab /etc/cron.d/crontab 
RUN chmod +x /etc/cron.d/crontab


# run the crontab file
RUN crontab /etc/cron.d/crontab

RUN mkdir -p /var/log/supervisor

COPY services/cron/bin/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# CMD ["/usr/bin/supervisord", "-n"]

CMD cron -f &amp;amp; /usr/bin/supervisord -n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Supervisord config&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[supervisord]
nodaemon=true
user=root

[program:celeryworker]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=celery -A myapp.celery worker --loglevel=INFO
autostart=true
autorestart=true

[program:myapp_gunicorn]
command=gunicorn --bind 0.0.0.0:5000 wsgi:app --log-level=debug --workers=4
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sample crontab&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHELL=/bin/bash
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# run notify users every day at 1:05AM
5 1 * * *  flask --app myapp notify-users &amp;gt;&amp;gt; /var/log/cron.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this approach to work, your app has to be structured to use the &lt;a href="https://flask.palletsprojects.com/en/2.3.x/patterns/packages/" rel="noopener noreferrer"&gt;package pattern&lt;/a&gt;. (This is same in the &lt;a href="https://dev.to/andychukse/building-a-user-authentication-api-using-python-flask-and-mysql-4lki"&gt;previous post&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This way, you can run a function from the command line on your app like below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;flask --app myapp notify-users&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Remember to specify a function to run on command line by using the &lt;code&gt;@app.cli.command&lt;/code&gt; to create custom commands&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from myapp.users import users
from myapp import app
from myapp.models.user import User
from myapp.queue.sendmail import send_email_to_user

@app.cli.command('notify-users')
def notify_users():
    offset = 0
    limit = 100
    users = User.filter(User.is_verified == 1).order_by(User.created_at.desc()).limit(limit).offset(offset)

    for user in users:
        send_email_to_user(user)

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Nginx Dockerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM nginx:1.23-alpine

RUN rm /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx/

RUN rm /etc/nginx/conf.d/default.conf
COPY myapp.conf /etc/nginx/conf.d/


CMD ["nginx", "-g", "daemon off;"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now use docker-compose to manage all containers&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sample &lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

services:
  backend:
    container_name: "app"
    build:
      context: .
      args:
        - "UID=-1000"
        - "GID=-1000"
        - "FLASK_DEBUG=false"
    volumes:
      - .:/app
    ports:
      - "5000:5000"
    env_file:
      - ".env"
    restart: "-unless-stopped"
    stop_grace_period: "2s"
    tty: true
    deploy:
      resources:
        limits:
          cpus: "-0"
          memory: "-0"
    depends_on:
      - "redis"
    profiles: ["myapp"]

  cron:
    container_name: "cron"
    build:
      context: .
      dockerfile: ./services/cron/Dockerfile
      args:
        - "UID=-1000"
        - "GID=-1000"
    env_file:
      - ".env"
    restart: "-unless-stopped"
    stop_grace_period: "2s"
    tty: true
    deploy:
      resources:
        limits:
          cpus: "-0"
          memory: "-0"
    depends_on:
      - "redis"
    volumes:
      - .:/cronapp/
    profiles: ["myapp"]

  redis:
    deploy:
      resources:
        limits:
          cpus: "-0"
          memory: "-0"
    image: "redis:7.0.5-bullseye"
    restart: "-unless-stopped"
    stop_grace_period: "3s"
    command: "redis-server --bind redis --maxmemory 256mb --maxmemory-policy allkeys-lru --appendonly yes"
    volumes:
      - "./redis:/data"
    profiles: ["redis"]

  nginx:
    container_name: "nginx"
    build:
      context: ./services/nginx
    restart: "-unless-stopped"
    stop_grace_period: "2s"
    tty: true
    deploy:
      resources:
        limits:
          cpus: "-0"
          memory: "-0"
    ports:
      - "80:80"
    depends_on:
      - "backend"
    volumes:
      - .:/nginx/
    profiles: ["nginx"]

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

&lt;/div&gt;



&lt;p&gt;You can now start your application and all services by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose up --detach --build app redis cron nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>nocode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Building a User Authentication API using Python Flask and MySQL</title>
      <dc:creator>Andrew Eze</dc:creator>
      <pubDate>Tue, 10 Jan 2023 18:03:24 +0000</pubDate>
      <link>https://dev.to/andychukse/building-a-user-authentication-api-using-python-flask-and-mysql-4lki</link>
      <guid>https://dev.to/andychukse/building-a-user-authentication-api-using-python-flask-and-mysql-4lki</guid>
      <description>&lt;p&gt;Recently, I decided to learn Python, as part of learning I built a remote jobs (&lt;a href="https://remote4africa.com" rel="noopener noreferrer"&gt;remote4africa&lt;/a&gt;) platform using Python Flask. In this post, I will show you step by step process for building a user authentication API using Python (Flask) and MySQL. The application will allow users to register and verify their email through an OTP Code sent to their email.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This post assumes you have an understanding of Python Flask and MySQL.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Flask is a micro web framework written in Python. It allows python developers to build APIs or full web app (frontend and Backend).&lt;/p&gt;

&lt;p&gt;When building a user authentication system, you should consider security and ease of use. &lt;br&gt;
On security, you should not allow users to use weak passwords, especially if you are working on a critical application. You should also encrypt the password before storing in your database.&lt;/p&gt;

&lt;p&gt;To build this I used the following dependencies:&lt;br&gt;
&lt;strong&gt;cerberus&lt;/strong&gt;: For validation&lt;br&gt;
&lt;strong&gt;alembic&lt;/strong&gt;: For Database Migration and Seeding&lt;br&gt;
&lt;strong&gt;mysqlclient&lt;/strong&gt;: For connecting to MySQL&lt;br&gt;
&lt;strong&gt;Flask-SQLAlchemy&lt;/strong&gt;, &lt;strong&gt;flask-marshmallow&lt;/strong&gt; and &lt;strong&gt;marshmallow-sqlalchemy&lt;/strong&gt;: For database object relational mapping&lt;br&gt;
&lt;strong&gt;pyjwt&lt;/strong&gt;: For JWT token generation&lt;br&gt;
&lt;strong&gt;Flask-Mail&lt;/strong&gt;: For email sending&lt;br&gt;
&lt;strong&gt;celery&lt;/strong&gt; and &lt;strong&gt;redis&lt;/strong&gt;: For queue management&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So let's get started&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Install and Set up your Flask Project.
&lt;/h2&gt;

&lt;p&gt;You can follow the guide at Flask Official Documentation site or follow the steps below. Please ensure you have python3 and pip installed in your machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir flaskauth
$ cd flaskauth
$ python3 -m venv venv
$ . venv/bin/activate
$ pip install Flask # pip install requirements.txt 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can install your all the required packages together with Flask at once using a requirements.txt file. (This is provided in the &lt;a href="https://github.com/andychukse/flaskauth" rel="noopener noreferrer"&gt;source code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Python Flask App folder Structure&lt;/strong&gt;&lt;br&gt;
It is always advisable to use the package pattern to organise your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/yourapplication
    /yourapplication
        __init__.py
        /static
            style.css
        /templates
            layout.html
            index.html
            login.html
            ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;pyproject.toml&lt;/code&gt; or &lt;code&gt;setup.py&lt;/code&gt; file next to the inner &lt;code&gt;yourapplication&lt;/code&gt; folder with the following contents:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pyproject.toml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "yourapplication"
description = "yourapplication description"
readme = "README.rst"
version="1.0.0"
requires-python = "&amp;gt;=3.11"
dependencies = [
    "Flask",
    "SQLAlchemy",
    "Flask-SQLAlchemy", 
    "wheel",
    "pyjwt",
    "datetime",
    "uuid",
    "pytest",
    "coverage",
    "python-dotenv",
    "alembic",
    "mysqlclient",
    "flask-marshmallow",
    "marshmallow-sqlalchemy",
    "cerberus",
    "Flask-Mail",
    "celery",
    "redis"
]

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;setup.py&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from setuptools import setup

setup(
    name='yourapplication',
    packages=['yourapplication'],
    include_package_data=True,
    install_requires=[
        'flask',
    ],
    py_modules=['config']
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then install your application so it is importable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install -e .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use the flask command and run your application with the &lt;code&gt;--app&lt;/code&gt; option that tells Flask where to find the application instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ flask –app yourapplication run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our own case the application folder looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/flaskauth
    /flaskauth
        __init__.py
        /models
        /auth
        /controllers
        /service
        /queue
        /templates
    config.py
    setup.py
    pyproject.toml
    /tests
    .env
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Set up Database and Models
&lt;/h2&gt;

&lt;p&gt;Since this is a simple application, we just need few tables for our database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;users&lt;/li&gt;
&lt;li&gt;countries&lt;/li&gt;
&lt;li&gt;refresh_tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We create our baseModel &lt;code&gt;models/base_model.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from sqlalchemy.ext.declarative import declared_attr
from flaskauth import app

_PLURALS = {"y": "ies"}

db = SQLAlchemy() 
ma = Marshmallow(app)


class BaseModel(object):
    @declared_attr
    def __tablename__(cls):
        name = cls.__name__
        if _PLURALS.get(name[-1].lower(), False):
            name = name[:-1] + _PLURALS[name[-1].lower()]
        else:
            name = name + "s"
        return name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Users table, we need email, first_name, last_name, password and other fields shown below. So we create the user and refresh token model &lt;code&gt;models/user.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from datetime import datetime
from flaskauth.models.base_model import BaseModel, db, ma
from flaskauth.models.country import Country

class User(db.Model, BaseModel):
    __tablename__ = "users"
    id = db.Column(db.BigInteger, primary_key=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(200), nullable=True)
    first_name = db.Column(db.String(200), nullable=False)
    last_name = db.Column(db.String(200), nullable=False)
    avatar = db.Column(db.String(250), nullable=True)
    country_id = db.Column(db.Integer, db.ForeignKey('countries.id', onupdate='CASCADE', ondelete='SET NULL'),
        nullable=True)
    is_verified = db.Column(db.Boolean, default=False, nullable=False)
    verification_code = db.Column(db.String(200), nullable=True)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.utcnow)
    deleted_at = db.Column(db.DateTime, nullable=True)
    country = db.relationship('Country', backref=db.backref('users', lazy=True))
    refresh_tokens = db.relationship('RefreshToken', backref=db.backref('users', lazy=True))


class RefreshToken(db.Model, BaseModel):
    __tablename__ = "refresh_tokens"
    id = db.Column(db.BigInteger, primary_key=True)
    token = db.Column(db.String(200), unique=True, nullable=False)
    user_id = db.Column(db.BigInteger, db.ForeignKey(User.id, onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False)
    expired_at = db.Column(db.DateTime, nullable=False)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.utcnow)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also create the country model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from flaskauth.models.base_model import BaseModel, db
from flaskauth.models.region import Region

class Country(db.Model, BaseModel):
    __tablename__ = "countries"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), unique=True, nullable=False)
    code = db.Column(db.String(3), unique=True, nullable=False)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For migration and seeding (creating the tables on the database and importing default data), we will use alembic. I will show you how to do this later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Create the &lt;code&gt;__init__.py&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;In this file we will initiate the flask application, establish database connection and also set up the queue. The &lt;strong&gt;init&lt;/strong&gt;.py file makes Python treat directories containing it as modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
from flask import Flask, make_response, jsonify
from jsonschema import ValidationError



def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)

    if test_config is None:
        app.config.from_object('config.ProductionConfig')
    else:
        app.config.from_object('config.DevelopmentConfig')

    # ensure the instance folder exists
    try:
        os.makedirs(app.instance_path)
    except OSError:
        pass

    return app

app = create_app(test_config=None)

from flaskauth.models.base_model import db, BaseModel
db.init_app(app)

from flaskauth.models.user import User
from celery import Celery

def make_celery(app):
    celery = Celery(app.name)
    celery.conf.update(app.config["CELERY_CONFIG"])

    class ContextTask(celery.Task):
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return self.run(*args, **kwargs)

    celery.Task = ContextTask
    return celery

celery = make_celery(app)

from flaskauth.auth import auth
from flaskauth.queue import queue
from flaskauth.controllers import user

app.register_blueprint(auth, url_prefix='/auth')
app.register_blueprint(queue)

@app.route("/hello")
def hello_message() -&amp;gt; str:
    return jsonify({"message": "Hello It Works"})


@app.errorhandler(400)
def bad_request(error):
    if isinstance(error.description, ValidationError):
        original_error = error.description
        return make_response(jsonify({'error': original_error.message}), 400)
    # handle other "Bad Request"-errors
    # return error
    return make_response(jsonify({'error': error.description}), 400)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file loads our application by calling all the models and controllers needed.&lt;/p&gt;

&lt;p&gt;The first function create_app is for creating a global Flask instance, it is the assigned to app&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app = create_app(test_config=None)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then import our database and base model from &lt;em&gt;models/base_model.py&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;We are using SQLAlchemy as ORM for our database, we also use Marshmallow for serialization/deserialization of our database objects.&lt;br&gt;
With the imported db, we initiate the db connection.&lt;/p&gt;

&lt;p&gt;Next we use the &lt;code&gt;make_celery(app)&lt;/code&gt; to initiate the celery instance to handle queue and email sending.&lt;/p&gt;

&lt;p&gt;Next we import the main parts of our applications (models, controllers and other functions).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.register_blueprint(auth, url_prefix='/auth')
app.register_blueprint(queue)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above will register the queue and auth blueprints. In the auth blueprint we handle all routes that starts with /auth&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.route("/hello")
def hello_message() -&amp;gt; str:
    return jsonify({"message": "Hello It Works"})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use this to test that our application is running.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;bad_request(error):&lt;/code&gt; function will handle any errors not handled by our application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Authentication
&lt;/h2&gt;

&lt;p&gt;To handle authentication we will create a file &lt;code&gt;auth/controllers.py&lt;/code&gt;&lt;br&gt;
This file has the register function that will handle POST request. We also need to handle data validation to ensure the user is sending the right information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from werkzeug.security import generate_password_hash, check_password_hash
from flaskauth import app
from flaskauth.auth import auth
from flask import request, make_response, jsonify, g, url_for
from datetime import timedelta, datetime as dt
from flaskauth.models.user import db, User, RefreshToken, UserSchema
from sqlalchemy.exc import SQLAlchemyError
from cerberus import Validator, errors
from flaskauth.service.errorhandler import CustomErrorHandler
from flaskauth.queue.email import send_email
from flaskauth.service.tokenservice import otp, secret, jwtEncode
from flaskauth.service.api_response import success, error

@auth.route("/register", methods=['POST'])
def register():
    schema = {
        'email': {
            'type': 'string',
            'required': True,
            'regex': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
        }, 
        'password': {
            'type': 'string', 
            'required': True,
            'min': 6

        },
        'first_name': {
            'type': 'string',
            'required': True,
            'min': 2

        },
        'last_name': {
            'type': 'string',
            'required': True, 
            'min': 2,
        }
    }

    v = Validator(schema, error_handler=CustomErrorHandler)
    form_data = request.get_json()
    args = request.args

    if(v.validate(form_data, schema) == False):
        return v.errors


    email = form_data['email']
    verification_code = otp(7)

    try:
        new_user = User(
            email= form_data['email'],
            password = generate_password_hash(form_data['password']),
            first_name= form_data['first_name'],
            last_name= form_data['last_name'],
            verification_code = secret(verification_code),
        )
        db.session.add(new_user)
        db.session.commit()

    except SQLAlchemyError as e:
        # error = str(e.__dict__['orig'])
        message = str(e)
        return error({}, message, 400)
        # return make_response(jsonify({'error': error}), 400)


    # Send verification email
    appName = app.config["APP_NAME"].capitalize()

    email_data = {
    'subject': 'Account Verification on ' + appName, 
    'to': email,
    'body': '',
    'name': form_data['first_name'],
    'callBack': verification_code,
    'template': 'verification_email'
    }

    send_email.delay(email_data)

    message = 'Registration Successful, check your email for OTP code to verify your account'
    return success({}, message, 200)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For validation I used &lt;code&gt;cerberus&lt;/code&gt;.  Cerberus is a python package that makes validation easy, it returns errors in json format when a validation fail. You can also provide custom error message like below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from cerberus import errors

class CustomErrorHandler(errors.BasicErrorHandler):
    messages = errors.BasicErrorHandler.messages.copy()
    messages[errors.REGEX_MISMATCH.code] = 'Invalid Email!'
    messages[errors.REQUIRED_FIELD.code] = '{field} is required!'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The register function validates the data, if successful, we then generate an otp/verification code using the &lt;code&gt;otp(total)&lt;/code&gt; function. We then hash the code for storage in database using the &lt;code&gt;secret(code=None)&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def secret(code=None):
    if not code:
        code = str(datetime.utcnow()) + otp(5)
    return hashlib.sha224(code.encode("utf8")).hexdigest()


def otp(total):
    return str(''.join(random.choices(string.ascii_uppercase + string.digits, k=total)))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We hash the user's password using the &lt;code&gt;werkzeug.security&lt;/code&gt; &lt;code&gt;generate_password_hash&lt;/code&gt; inbuilt function in flask and store the record in database. We are using SQLAlchemy to handle this.&lt;/p&gt;

&lt;p&gt;After storing the user details, we schedule email to be sent immediately to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Send verification email
    appName = app.config["APP_NAME"].capitalize()

    email_data = {
    'subject': 'Account Verification on ' + appName, 
    'to': email,
    'body': '',
    'name': form_data['first_name'],
    'callBack': verification_code,
    'template': 'verification_email'
    }

    send_email.delay(email_data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we return a response to the user&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;message = 'Registration Successful, check your email for OTP code to verify your account'

return success({}, message, 200)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To handle responses, I created a &lt;code&gt;success&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; functions under &lt;code&gt;services/api_response.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from flask import make_response, jsonify

def success(data, message: str=None, code: int = 200):
    data['status'] = 'Success'
    data['message'] = message
    data['success'] = True

    return make_response(jsonify(data), code)


def error(data, message: str, code: int):
    data['status'] = 'Error'
    data['message'] = message
    data['success'] = False

    return make_response(jsonify(data), code)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have other functions to handle email verification with OTP, login and refresh_token.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify account
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@auth.route("/verify", methods=['POST'])
def verifyAccount():
    schema = {
        'otp': {
            'type': 'string', 
            'required': True,
            'min': 6

        },
    }
    v = Validator(schema, error_handler=CustomErrorHandler)
    form_data = request.get_json()

    if(v.validate(form_data, schema) == False):
        return v.errors

    otp = form_data['otp']
    hash_otp = secret(otp)
    user = User.query.filter_by(verification_code = hash_otp).first()

    if not user:
        message = 'Failed to verify account, Invalid OTP Code'
        return error({},message, 401)


    user.verification_code = None
    user.is_verified = True
    db.session.commit()


    message = 'Verification Successful! Login to your account'
    return success({}, message, 200)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Login user
&lt;/h3&gt;

&lt;p&gt;This function handles POST request and validates the user's email and password. We first check if a record with the user's email exists, if not, we return an error response. If user's email exists, we &lt;code&gt;check_password_hash&lt;/code&gt; function to check if the password supplied is valid.&lt;br&gt;
If the password is valid, we call the &lt;code&gt;authenticated&lt;/code&gt; function to generate and store a refresh token, generate a JWT token and the return a response with both tokens to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@auth.route("/login", methods=['POST'])
def login():
    schema = {
        'email': {
            'type': 'string',
            'required': True,
            'regex': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
        }, 
        'password': {
            'type': 'string', 
            'required': True,
            'min': 6

        },
    }
    v = Validator(schema, error_handler=CustomErrorHandler)
    form_data = request.get_json()

    user = User.query.filter_by(email =  form_data['email']).first()

    if not user:
        message = 'Login failed! Invalid account.'
        return error({}, message, 401)

    if not check_password_hash(user.password, form_data['password']):
        message = 'Login failed! Invalid password.'
        return error({}, message, 401)

    return authenticated(user)


def authenticated(user: User):
    refresh_token = secret()

    try:
        refreshToken = RefreshToken(
                user_id = user.id,
                token = refresh_token,
                expired_at = dt.utcnow() + timedelta(minutes = int(app.config['REFRESH_TOKEN_DURATION']))
            )
        db.session.add(refreshToken)
        db.session.commit()

    except SQLAlchemyError as e:
        # error = str(e.__dict__['orig'])
        message = str(e)
        return error({}, message, 400)
    # del user['password']
    user_schema = UserSchema()
    data = {
        "token": jwtEncode(user),
        "refresh_token": refresh_token,
        "user": user_schema.dump(user)
    }
    message = "Login Successful, Welcome Back"
    return success(data, message, 200)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response with the token will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "expired_at": "Wed, 10 Jan 2024 16:43:48 GMT",
    "message": "Login Successful, Welcome Back",
    "refresh_token": "a5b5ehghghjk8truur9kj4f999bf6c01d34892df768",
    "status": "Success",
    "success": true,
    "token": "eyK0eCAiOiJDhQiLCJhbGciOI1NiJ9.eyJzdWIiOjEsImlhdCI6MTY3MzM2OTAyOCwiZXhwIjoxNjczOTczODI4fQ.a6fn7z8v9K5EmqZO7-J8VkY2u_Kdffh8aOVuWjTH138",
    "user": {
        "avatar": null,
        "country": {
            "code": "NG",
            "id": 158,
            "name": "Nigeria"
        },
        "country_id": 158,
        "created_at": "2022-12-09T16:00:48",
        "email": "user@example.com",
        "first_name": "John",
        "id": 145,
        "is_verified": true,
        "last_name": "Doe",
        "updated_at": "2022-12-10T12:17:45"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The refresh token is useful for keeping a user logged without requesting for login information every time. Once JWT token is expired a user can use the refresh token to request a new JWT token. This can be handled by the frontend without asking the user for login details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@auth.route("/refresh", methods=['POST'])
def refreshToken():
    schema = {
        'refresh_token': {
            'type': 'string',
            'required': True,
        },
    }
    v = Validator(schema, error_handler=CustomErrorHandler)
    form_data = request.get_json()
    now = dt.utcnow()
    refresh_token = RefreshToken.query.filter(token == form_data['refresh_token'], expired_at &amp;gt;= now).first()

    if not refresh_token:
        message = "Token expired, please login"
        return error({}, message, 401)

    user =  User.query.filter_by(id = refresh_token.user_id).first()

    if not user:
        message = "Invalid User"
        return error({}, message, 403)

    data = {
        "token": jwtEncode(user),
        "id": user.id
    }
    message = "Token Successfully refreshed"
    return success(data, message, 200)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;You can access the full source code on &lt;a href="https://github.com/andychukse/flaskauth" rel="noopener noreferrer"&gt;GitHub HERE&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Install and Run the application
&lt;/h2&gt;

&lt;p&gt;You can run the application by executing the following on your terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask --app flaskauth run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure the virtual environment is active and you're on the root project folder when you run this. &lt;br&gt;
Alternatively, you don't need to be in the root project folder to run the command if you installed the application using the command below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install -e .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To ensure email is delivering set up SMTP crediential in the .env file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_NAME=flaskauth
FLASK_APP=flaskauth
FLASK_DEBUG=True
FLASK_TESTING=False
SQLALCHEMY_DATABASE_URI=mysql://root:@localhost/flaskauth_db?charset=utf8mb4

MAIL_SERVER='smtp.mailtrap.io'
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_PORT=2525
MAIL_USE_TLS=True
MAIL_USE_SSL=False
MAIL_DEFAULT_SENDER='info@flaskauth.app'
MAIL_DEBUG=True

SERVER_NAME=
AWS_SECRET_KEY=
AWS_KEY_ID=
AWS_BUCKET=

JWT_DURATION=10080
REFRESH_TOKEN_DURATION=525600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, run the celery application to handle queue and email sending&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;celery -A flaskauth.celery worker --loglevel=INFO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Run Database Migration and Seeding
&lt;/h2&gt;

&lt;p&gt;We will use alembic package to handle migration. Alembic is already installed as part of our requirements. Also, see the source code for the migration files. We will run the following to migrate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alembic upgrade head
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use alembic to generate database migrations. For example, to create users table run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alembic revision -m "create users table"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a migration file, similar to the following:&lt;br&gt;
&lt;/p&gt;

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

Revision ID: 9d4a5cb3f558
Revises: 5b9768c1b705
Create Date: 2023-01-10 18:34:09.608403

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '9d4a5cb3f558'
down_revision = '5b9768c1b705'
branch_labels = None
depends_on = None


def upgrade() -&amp;gt; None:
    pass


def downgrade() -&amp;gt; None:
    pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then edit it to the following:&lt;br&gt;
&lt;/p&gt;

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

Revision ID: 9d4a5cb3f558
Revises: None
Create Date: 2023-01-10 18:34:09.608403

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '9d4a5cb3f558'
down_revision = None
branch_labels = None
depends_on = None


def upgrade() -&amp;gt; None:
    op.create_table('users',
    sa.Column('id', sa.BigInteger(), nullable=False),
    sa.Column('email', sa.String(length=120), nullable=False),
    sa.Column('password', sa.String(length=200), nullable=True),
    sa.Column('first_name', sa.String(length=200), nullable=False),
    sa.Column('last_name', sa.String(length=200), nullable=False),
    sa.Column('avatar', sa.String(length=250), nullable=True),
    sa.Column('country_id', sa.Integer(), nullable=True),
    sa.Column('is_verified', sa.Boolean(), nullable=False),
    sa.Column('verification_code', sa.String(length=200), nullable=True),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=True),
    sa.Column('deleted_at', sa.DateTime(), nullable=True),
    sa.ForeignKeyConstraint(['country_id'], ['countries.id'], onupdate='CASCADE', ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('email')
    )
    # ### end Alembic commands ###


def downgrade() -&amp;gt; None:
    op.drop_table('users')
    # ### end Alembic commands ###
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also auto generate migration files from your models using the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alembic revision --autogenerate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: For auto generation to work, you have to import your app context into the alembic &lt;code&gt;env.py&lt;/code&gt; file. See source code for example.&lt;/p&gt;

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

&lt;p&gt;After completing the database migration, you can go ahead and test the app buy sending requests to &lt;a href="http://localhost:5000/auth/register" rel="noopener noreferrer"&gt;http://localhost:5000/auth/register&lt;/a&gt; using Postman, remember to supply all the necessary data, example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "email": "ade@example.com",
    "password": "password",
    "first_name": "Ade",
    "last_name": "Emeka"
    "country": "NG"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me know your thoughts and feedback below.&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>api</category>
      <category>mysql</category>
    </item>
    <item>
      <title>How to install pdf2htmlEX in Ubuntu 20.04</title>
      <dc:creator>Andrew Eze</dc:creator>
      <pubDate>Mon, 30 Nov 2020 14:37:44 +0000</pubDate>
      <link>https://dev.to/andychukse/how-to-install-pdf2htmlex-in-ubuntu-20-04-2c15</link>
      <guid>https://dev.to/andychukse/how-to-install-pdf2htmlex-in-ubuntu-20-04-2c15</guid>
      <description>&lt;p&gt;pdf2htmlEX is a tool that allows you to convert PDF to HTML without losing text or format. pdf2htmlEX renders PDF files in HTML, using modern Web technologies. It is very useful if you want to convert academic papers with lots of formulas and figures to HTML format&lt;/p&gt;

&lt;p&gt;This post will show you how to install pdf2htmlEX on Ubuntu 20.04 LTS.&lt;/p&gt;

&lt;p&gt;As at the time of writing this post pdf2htmlEX is no longer packaged by Debian/Ubuntu, you will need to install from the pdf2htmlEX Debian archives (*.deb).&lt;/p&gt;

&lt;p&gt;To get started you will need to install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt install -y libfontconfig1 libcairo2 libjpeg-turbo8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you get error about unmet dependencies run the following to fix broken packages&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt apt --fix-broken install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Download latest *.deb package from &lt;a href="https://github.com/pdf2htmlEX/pdf2htmlEX/releases" rel="noopener noreferrer"&gt;pdf2htmlEX repository&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget https://github.com/pdf2htmlEX/pdf2htmlEX/releases/download/v0.18.8.rc1/pdf2htmlEX-0.18.8.rc1-master-20200630-Ubuntu-bionic-x86_64.deb
sudo mv pdf2htmlEX-0.18.8.rc1-master-20200630-Ubuntu-bionic-x86_64.deb pdf2htmlEX.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the package&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install ./pdf2htmlEX.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;It is very important that you use a (relative or absolute) path to the *.deb file. It is the ./ in front of the pdf2htmlEX.deb file name which tells apt install that it is supposed to install a local file rather than a package name in apt install's internal package database.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alternatively you could use the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo dpkg -i pdf2htmlEX.deb
sudo apt install -f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test your installation&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You should see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pdf2htmlEX version 0.18.8.rc1
Copyright 2012-2015 Lu Wang &amp;lt;coolwanglu@gmail.com&amp;gt; and other contributors
Libraries: 
  poppler 0.89.0
  libfontforge (date) 20200314
  cairo 1.16.0
Default data-dir: /usr/local/share/pdf2htmlEX
Poppler data-dir: /usr/local/share/pdf2htmlEX/poppler
Supported image format: png jpg svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>pdf2html</category>
      <category>pdftohtml</category>
      <category>pdf2htmlex</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>How to install pdf2htmlEX in Ubuntu 18.0.4</title>
      <dc:creator>Andrew Eze</dc:creator>
      <pubDate>Tue, 11 Feb 2020 15:48:32 +0000</pubDate>
      <link>https://dev.to/andychukse/how-to-install-pdf2htmlex-in-ubuntu-18-0-4-d3b</link>
      <guid>https://dev.to/andychukse/how-to-install-pdf2htmlex-in-ubuntu-18-0-4-d3b</guid>
      <description>&lt;p&gt;pdf2htmlEX is a tool that allows you to convert PDF to HTML without losing text or format. pdf2htmlEX renders PDF files in HTML, using modern Web technologies. It is very useful if you want to convert academic papers with lots of formulas and figures to HTML format&lt;/p&gt;

&lt;p&gt;This post will show you how to install pdf2htmlEX to work with poppler-0.62.0 (as installed from the Ubuntu 18.04 LTS package).&lt;/p&gt;

&lt;p&gt;As at the time of writing this post pdf2htmlEX is no longer packaged by Debian/Ubuntu, you will need to compile pdf2htmlEX yourself.&lt;/p&gt;

&lt;p&gt;To get started you will need to install the dependencies&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get update

sudo apt-get install -y libpoppler-private-dev pkg-config cmake make gcc g++ 
libcairo-dev libspiro-dev libpng-dev libjpeg-dev libpoppler-dev libpango1.0- 
dev libfontforge-dev poppler-data openjdk-8-jre-headless libopenjp2-7-dev libopenjp2-7 libgdk-pixbuf2.0-dev libfontconfig1-dev poppler-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Download a copy of the repository&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget https://github.com/pdf2htmlEX/pdf2htmlEX/archive/v0.16.0-poppler-0.62.0-ubuntu-18.04.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Unzip and install&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unzip v0.16.0-poppler-0.62.0-ubuntu-18.04.zip
cd pdf2htmlEX-0.16.0-poppler-0.62.0-ubuntu-18.04
./dobuild
./doinstall
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you encounter error about package not found like&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pdf2htmlEX: error while loading shared libraries: libfontforge.so.4: cannot open shared object file: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Check if the package is installed, in this case libfontforge&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dpkg -l | grep fontforge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should see the following&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ii  fontforge            1:20170731~dfsg-1    amd64     font editor
  ii  fontforge-common     1:20170731~dfsg-1    all       font editor (common files)
  ii  libfontforge-dev     1:20170731~dfsg-1    amd64     font editor - runtime library (development files)
  ii  libfontforge2        1:20170731~dfsg-1    amd64     font editor - runtime library
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you don't see the package, install it. However, if you have installed the packages and still got the error, type the following to set the path.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

</description>
      <category>pdf2htmlex</category>
      <category>ubuntu</category>
      <category>pdftohtml</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
