πŸš€πŸš€ A Shopify App template for serverless, non-embedded Apps.

Overview

πŸš€ Free Shopify x Next.js App Template for serverless non-embedded Apps

Everything to build your next non-embedded Shopify App and Marketing pages in one place. This Template utilizes Middleware and APIs for OAuth, so no custom server is needed.

Intentionally barebones. 🦴

Table of Contents

  • 🀩 Features
  • πŸ‘€ Requirements
  • πŸ€“ Getting Started
  • πŸš€ One click deploy
  • 🧰 Built with

🀩 Features

  • ⚑ Next.js - React Framework for static rendering
  • ✨ Serverless Architecture
  • πŸ’³ App Subscrptions
  • πŸ’Ύ Session Storage with Redis
  • πŸš‡ Localtunnel for development
  • πŸš€ Apollo/Client
  • πŸͺ Webhooks set up

πŸ‘€ Requirements

  • Shopify Partner Account
  • Shopify Dev Store
  • Upstash Redis Database

πŸ€“ Getting Started

  • Click Use this template or this link
  • Create an App in your Shopify Partner Account
    • Set https://localhost as the App Url for now
    • Go to App Setup -> Embedded app and disable Embed your app in Shopify admin
  • Fill out your .env file
    • SHOPIFY_API_KEY: The Shopify Api key of the app, you have just created
    • SHOPIFY_API_SECRET_KEY: The Shopify Api secret key of the app, you have just created
    • SCOPES: The access scopes your app needs
    • HOST: The Url of your app. Leave this empty for development
    • SHOP: Your dev stores url
    • DEV_APP_SUBDOMAIN: Your desired localtunnel subdomain. If it isn't available, you will get assigned a random subdomain.
    • UPSTASH_REDIS_REST_URL: Your Upstash Redis REST url.
    • UPSTASH_REDIS_REST_TOKEN: Your Upstash Redis REST token.
  • Run npm run install
  • Run npm run dev
  • Set your App Urls in the partner dashboard. Your Apps localtunnel Url will be displayed in the console on npm run dev and written to your .env file
    • App Url: https://{YOUR_APP_URL}/app
    • Allowed redirection Urls
      • https://{YOUR_APP_URL}/api/auth
      • https://{YOUR_APP_URL}/api/auth/callback
      • https://{YOUR_APP_URL}/api/auth/offline
      • https://{YOUR_APP_URL}/api/auth/offline-callback
  • Visit https://{YOUR_APP_URL}/login to install your app

πŸš€ One click deploy

Clone and deploy this template in one click to Vercel for free!

Deploy with Vercel

Check out our Next.js deployment documentation for more details.

🧰 Built with

Comments
  • Update AppSettings through Shopify API

    Update AppSettings through Shopify API

    Why LocalTunnel instead of Ngrok? And how can we update AppSettings through Shopify API? If that is possible... Shopify makes it really painful to change it every time with ngrok always changing URL's all the time.

    opened by aimproxy 11
  • NgrokClientError: failed to start tunnel

    NgrokClientError: failed to start tunnel

    The error I am getting is (file paths shortened, ngrok auth token removed):

    npm run dev
    
    > [email protected] dev
    > next dev
    
    ready - started server on 0.0.0.0:3000, url: http://localhost:3000
    info  - Loaded env from .../shopify-non-embedded-app-template/.env
    NgrokClientError: failed to start tunnel
        at NgrokClient.request (.../shopify-non-embedded-app-template/node_modules/ngrok/src/client.js:33:23)
        at processTicksAndRejections (node:internal/process/task_queues:96:5)
        at connectRetry (.../shopify-non-embedded-app-template/node_modules/ngrok/index.js:29:22)
        at setEnvironmentAndReturnHost (file://.../shopify-non-embedded-app-template/next.config.mjs:43:20)
        at nextConfig (file://.../shopify-non-embedded-app-template/next.config.mjs:61:10)
        at Object.normalizeConfig (.../shopify-non-embedded-app-template/node_modules/next/server/config-shared.ts:512:10)
        at Object.loadConfig [as default] (.../shopify-non-embedded-app-template/node_modules/next/server/config.ts:682:24)
        at NextServer.loadConfig (.../shopify-non-embedded-app-template/node_modules/next/server/next.ts:132:18)
        at NextServer.prepare (.../shopify-non-embedded-app-template/node_modules/next/server/next.ts:109:20)
        at .../shopify-non-embedded-app-template/node_modules/next/cli/next-dev.ts:105:7 {
      response: <ref *1> IncomingMessage {
        _readableState: ReadableState {
          objectMode: false,
          highWaterMark: 16384,
          buffer: BufferList { head: null, tail: null, length: 0 },
          length: 0,
          pipes: [],
          flowing: false,
          ended: true,
          endEmitted: true,
          reading: false,
          constructed: true,
          sync: false,
          needReadable: false,
          emittedReadable: false,
          readableListening: true,
          resumeScheduled: false,
          errorEmitted: false,
          emitClose: true,
          autoDestroy: true,
          destroyed: true,
          errored: null,
          closed: true,
          closeEmitted: true,
          defaultEncoding: 'utf8',
          awaitDrainWriters: null,
          multiAwaitDrain: false,
          readingMore: false,
          dataEmitted: true,
          decoder: null,
          encoding: null,
          [Symbol(kPaused)]: null
        },
        _events: [Object: null prototype] {
          end: [Function: responseOnEnd],
          aborted: [Array],
          error: [Function],
          readable: [Function (anonymous)]
        },
        _eventsCount: 4,
        _maxListeners: undefined,
        socket: Socket {
          connecting: false,
          _hadError: false,
          _parent: null,
          _host: null,
          _readableState: [ReadableState],
          _events: [Object: null prototype],
          _eventsCount: 7,
          _maxListeners: undefined,
          _writableState: [WritableState],
          allowHalfOpen: false,
          _sockname: null,
          _pendingData: null,
          _pendingEncoding: '',
          server: null,
          _server: null,
          parser: null,
          _httpMessage: [ClientRequest],
          _peername: [Object],
          [Symbol(async_id_symbol)]: 327,
          [Symbol(kHandle)]: [TCP],
          [Symbol(kSetNoDelay)]: false,
          [Symbol(lastWriteQueueSize)]: 0,
          [Symbol(timeout)]: null,
          [Symbol(kBuffer)]: null,
          [Symbol(kBufferCb)]: null,
          [Symbol(kBufferGen)]: null,
          [Symbol(kCapture)]: false,
          [Symbol(kBytesRead)]: 0,
          [Symbol(kBytesWritten)]: 0,
          [Symbol(RequestTimeout)]: undefined
        },
        httpVersionMajor: 1,
        httpVersionMinor: 1,
        httpVersion: '1.1',
        complete: true,
        rawHeaders: [
          'Content-Type',
          'application/json',
          'Date',
          'Sat, 02 Jul 2022 07:49:41 GMT',
          'Content-Length',
          '105',
          'Connection',
          'close'
        ],
        rawTrailers: [],
        aborted: false,
        upgrade: false,
        url: 'http://127.0.0.1:4041/api/tunnels',
        method: null,
        statusCode: 502,
        statusMessage: 'Bad Gateway',
        client: Socket {
          connecting: false,
          _hadError: false,
          _parent: null,
          _host: null,
          _readableState: [ReadableState],
          _events: [Object: null prototype],
          _eventsCount: 7,
          _maxListeners: undefined,
          _writableState: [WritableState],
          allowHalfOpen: false,
          _sockname: null,
          _pendingData: null,
          _pendingEncoding: '',
          server: null,
          _server: null,
          parser: null,
          _httpMessage: [ClientRequest],
          _peername: [Object],
          [Symbol(async_id_symbol)]: 327,
          [Symbol(kHandle)]: [TCP],
          [Symbol(kSetNoDelay)]: false,
          [Symbol(lastWriteQueueSize)]: 0,
          [Symbol(timeout)]: null,
          [Symbol(kBuffer)]: null,
          [Symbol(kBufferCb)]: null,
          [Symbol(kBufferGen)]: null,
          [Symbol(kCapture)]: false,
          [Symbol(kBytesRead)]: 0,
          [Symbol(kBytesWritten)]: 0,
          [Symbol(RequestTimeout)]: undefined
        },
        _consuming: true,
        _dumped: false,
        req: ClientRequest {
          _events: [Object: null prototype],
          _eventsCount: 10,
          _maxListeners: undefined,
          outputData: [],
          outputSize: 0,
          writable: true,
          destroyed: false,
          _last: true,
          chunkedEncoding: false,
          shouldKeepAlive: false,
          maxRequestsOnConnectionReached: false,
          _defaultKeepAlive: true,
          useChunkedEncodingByDefault: true,
          sendDate: false,
          _removedConnection: false,
          _removedContLen: false,
          _removedTE: false,
          _contentLength: null,
          _hasBody: true,
          _trailer: '',
          finished: true,
          _headerSent: true,
          _closed: false,
          socket: [Socket],
          _header: 'POST /api/tunnels HTTP/1.1\r\n' +
            'user-agent: got (https://github.com/sindresorhus/got)\r\n' +
            'content-type: application/json\r\n' +
            'accept: application/json\r\n' +
            'content-length: 138\r\n' +
            'accept-encoding: gzip, deflate, br\r\n' +
            'Host: 127.0.0.1:4041\r\n' +
            'Connection: close\r\n' +
            '\r\n',
          _keepAliveTimeout: 0,
          _onPendingData: [Function: nop],
          agent: [Agent],
          socketPath: undefined,
          method: 'POST',
          maxHeaderSize: undefined,
          insecureHTTPParser: undefined,
          path: '/api/tunnels',
          _ended: true,
          res: [Circular *1],
          aborted: false,
          timeoutCb: null,
          upgradeOrConnect: false,
          parser: null,
          maxHeadersCount: null,
          reusedSocket: false,
          host: '127.0.0.1',
          protocol: 'http:',
          timings: [Object],
          emit: [Function (anonymous)],
          [Symbol(kCapture)]: false,
          [Symbol(kNeedDrain)]: false,
          [Symbol(corked)]: 0,
          [Symbol(kOutHeaders)]: [Object: null prototype],
          [Symbol(reentry)]: true
        },
        timings: {
          start: 1656748180990,
          socket: 1656748180990,
          lookup: 1656748180991,
          connect: 1656748180991,
          secureConnect: undefined,
          upload: 1656748180991,
          response: 1656748181098,
          end: 1656748181098,
          error: undefined,
          abort: undefined,
          phases: [Object]
        },
        emit: [Function (anonymous)],
        requestUrl: 'http://127.0.0.1:4041/api/tunnels',
        redirectUrls: [],
        request: Request {
          _readableState: [ReadableState],
          _events: [Object: null prototype],
          _eventsCount: 14,
          _maxListeners: undefined,
          _writableState: [WritableState],
          allowHalfOpen: true,
          requestInitialized: true,
          redirects: [],
          retryCount: 0,
          _progressCallbacks: [],
          write: [Function: onLockedWrite],
          end: [Function: onLockedWrite],
          options: [Object],
          requestUrl: 'http://127.0.0.1:4041/api/tunnels',
          _cannotHaveBody: false,
          _noPipe: true,
          [Symbol(kCapture)]: false,
          [Symbol(downloadedSize)]: 105,
          [Symbol(uploadedSize)]: 138,
          [Symbol(serverResponsesPiped)]: Set(0) {},
          [Symbol(stopReading)]: true,
          [Symbol(triggerRead)]: false,
          [Symbol(jobs)]: [],
          [Symbol(body)]: '{"addr":3000,"authtoken":"<REMOVED>","proto":"http","name":"<REMOVED>"}',
          [Symbol(bodySize)]: 138,
          [Symbol(cancelTimeouts)]: [Function: cancelTimeouts],
          [Symbol(unproxyEvents)]: [Function (anonymous)],
          [Symbol(request)]: [ClientRequest],
          [Symbol(originalResponse)]: [Circular *1],
          [Symbol(isFromCache)]: false,
          [Symbol(responseSize)]: 105,
          [Symbol(response)]: [Circular *1],
          [Symbol(startedReading)]: true
        },
        isFromCache: false,
        ip: '127.0.0.1',
        retryCount: 0,
        rawBody: <Buffer 7b 22 65 72 72 6f 72 5f 63 6f 64 65 22 3a 31 30 33 2c 22 73 74 61 74 75 73 5f 63 6f 64 65 22 3a 35 30 32 2c 22 6d 73 67 22 3a 22 66 61 69 6c 65 64 20 ... 55 more bytes>,
        body: '{"error_code":103,"status_code":502,"msg":"failed to start tunnel","details":{"err":"remote gone away"}}\n',
        [Symbol(kCapture)]: false,
        [Symbol(kHeaders)]: {
          'content-type': 'application/json',
          date: 'Sat, 02 Jul 2022 07:49:41 GMT',
          'content-length': '105',
          connection: 'close'
        },
        [Symbol(kHeadersCount)]: 8,
        [Symbol(kTrailers)]: null,
        [Symbol(kTrailersCount)]: 0,
        [Symbol(RequestTimeout)]: undefined
      },
      body: {
        error_code: 103,
        status_code: 502,
        msg: 'failed to start tunnel',
        details: { err: 'remote gone away' }
      }
    }
    
    

    To recreate:

    1. Clone the repo to local
    2. Create .env file with SHOPIFY_API_KEY, SHOPIFY_API_SECRET, SHOP, SCOPES, NGROK_AUTH_TOKEN, UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN
    3. Executed npm run dev

    I have checked to ensure that the Ngrok tunnel is working.

    opened by nabusman 3
  • Concerns about Redis and Middleware

    Concerns about Redis and Middleware

    Hi, firstly I want to thank you all for the amazing job done here. I personally wanted to build a non-embedded app and I just started in de middle, I am a bit lost with Shopify Docs, but I am getting ahead thanks to your repo!

    I understand that the repo is maintained frequently, due to the last month commit! But there is a matter that I want to discuss with you guys. As you do store your session storage on Redis, I do the same thing. So I was wondering if CustomeSessionStorage could be replaced by RedisSessionStorage?

    Another issue, this time addressing the middleware, could Shopify.Utils.loadCurrentSession be used to the job? Instead of going looking for cookies? Isn't that what this function is all about? Correct me if I am wrong!

    Thx for your, keep up your amazing work guys!

    opened by aimproxy 2
  • fix: partners url update mutation variable key

    fix: partners url update mutation variable key

    When cloning and running yarn dev, I get this error:

    Error: Variable $applicationUrl of type Url! was provided invalid value: {"response":{"errors":[{"message":"Variable $applicationUrl of type Url! was provided invalid value","locations":[{"line":2,"column":40}],"extensions":{"value":null,"problems":[{"path":[],"explanation":"Expected value to not be null"}]}},{"message":"Variable $redirectUrlWhitelist of type [Url]! was provided invalid value","locations":[{"line":2,"column":63}],"extensions":{"value":null,"problems":[{"path":[],"explanation":"Expected value to not be null"}]}}],"status":200,"headers":{}},"request":{"query":"\n  mutation appUpdate($apiKey: String!, $applicationUrl: Url!, $redirectUrlWhitelist: [Url]!) {\n    appUpdate(input: {apiKey: $apiKey, applicationUrl: $applicationUrl, redirectUrlWhitelist: $redirectUrlWhitelist}) {\n      userErrors {\n        message\n        field\n      }\n    }\n  }\n",
    

    Here are the mutation variable keys from the Shopify CLI Kit.

    opened by anguy95 1
  • :ambulance: Replacing localtunnel with ngrok

    :ambulance: Replacing localtunnel with ngrok

    Localtunnel has been proven to be an unreliable solution for proxying the app in dev mode. Thus it has been replaced by Ngrok. #6

    In addition to that, the App URLs in the Shopify partners dashboard will be updated automatically with help of the Shopify CLI.

    opened by carstenlebek 1
  • Redirect not working correctly when the sessions are cleared in the DB

    Redirect not working correctly when the sessions are cleared in the DB

    These lines:

    https://github.com/carstenlebek/shopify-non-embedded-app-template/blob/4d50af598424f2dc59605c4bfce8b03f7b8ce635/src/pages/_middleware.js#L15-L27

    Should be:

    const response = await fetch(
      `${process.env.HOST}/api/auth/verify-session?sessionToken=${req.cookies["shopify_app_session"]}`
    );
    if (response.status === 200) {
      return NextResponse.next();
    } else {
      if (shop) {
        return NextResponse.redirect(
          `${process.env.HOST}/api/auth/offline?shop=${shop}`
        );
      } else {
        return NextResponse.redirect(`${process.env.HOST}/login`);
      }
    }
    
    
    opened by ivorpad 1
  • 404 not found

    404 not found

    I'm getting a 404 not found error after adding the demo store url and clicking on login.

    When I logged authRoute, it fetches authRoute fine, but this error happens when it hits the callback url.

    Any idea?

    opened by zifahm 0
  • No organization exception is thrown

    No organization exception is thrown

    Good evening,

    It's my first Shopify shop project and I wanted to use this template. I followed all you guide to create it, but on yarn dev the exception NoOrgError is always thrown. Is that because I did not configure my Shopify on the right way ? Should I use Shopify Plus to have organizations ?

    opened by stephraja 0
  • Only absolute URLs are supported

    Only absolute URLs are supported

    Hi,

    I am heaving issue with your app. I am not experienced with frontend development. I forked, setup .env, ngrok etc and tried to install it. When ngrok redirect me to localhost I have this error:

    Error: CustomSessionStorage failed to store a session. Error Details: TypeError: Only absolute URLs are supported

    Any idea what is wrong? I tried to track it, but this is unknown area for me.

    Full stack trace:

    Server Error Error: CustomSessionStorage failed to store a session. Error Details: TypeError: Only absolute URLs are supported

    This error happened while generating the page. Any console logs will be displayed in the terminal window. Call Stack SessionStorageError.ShopifyError [as constructor] file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/dist/error.js (13:28) new SessionStorageError file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/dist/error.js (186:42) CustomSessionStorage. file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/dist/auth/session/storage/custom.js (27:31) step file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js (144:27) Object.throw file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js (125:57) rejected file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js (116:69) processTicksAndRejections node:internal/process/task_queues (96:5)

    opened by woody41 0
An unofficial, simplified version of the @Shopify/koa-shopify-auth middleware library.

simple-koa-shopify-auth https://www.npmjs.com/package/simple-koa-shopify-auth NOTE: This package is not maintained by or affiliated with Shopify. Desc

David 20 Nov 7, 2022
Forked from hayes0724/shopify-packer Modern development tool for Shopify using Webpack 5. Easy to extend and customize, zero build config, compatible with Slate and existing websites.

Shopify Packer Modern development tool for Shopify using Webpack 5. Easy to extend and customize, zero build config, comes with starter themes and com

Web & Mobile | eCommerce | Full-Stack Developer 4 Nov 24, 2022
Shopify Landing (Open source landing page shopify application)

SHOPIFY Open source landing page shopify application Configuration and Setup Key Features Technologies used ?? Screenshots Author License Configuratio

Gilbert Hutapea 8 May 10, 2023
Grupprojekt fΓΆr kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet fΓΆr kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide FΓΆr information om hur utv

Svante Jonsson IT-HΓΆgskolan 3 May 18, 2022
Hemsida fΓΆr personer i Sverige som kan och vill erbjuda boende till mΓ€nniskor pΓ₯ flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo fΓΆr kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
A sample app (with TypeScript) to help developers bootstrap their Shopify app development.

⚠️ Please, refer to kanzitelli/shopify-app-template-typescript for the most up-to-date version. This repo was used for testing purposes. Shopify App N

Batyr 4 Jun 10, 2022
Learn Web 2.0 and Web 3.0 Development using Next.js, Typescript, AWS CDK, AWS Serverless, Ethereum and AWS Aurora Serverless

Learn Web 2.0 Cloud and Web 3.0 Development in Baby Steps In this course repo we will learn Web 2.0 cloud development using the latest state of the ar

Panacloud Multi-Cloud Internet-Scale Modern Global Apps 89 Jan 3, 2023
A Serverless GraphQL Sample project using Apollo and Serverless Framework with TypeScript and Webpack.

Serverless GraphQL Boilerplate This is a base project with a structure that includes Serverless Framework, Apollo, TypeScript and Webpack. It can be d

Ravi Souza 5 Aug 23, 2022
AWS Lambda & Serverless - Developer Guide with Hands-on Labs. Develop thousands line of aws lambda functions interact to aws serverless services with real-world hands-on labs

AWS Lambda & Serverless - Developer Guide with Hands-on Labs UDEMY COURSE WITH DISCOUNTED - Step by Step Development of this Repository -> https://www

awsrun 35 Dec 17, 2022
A simple template to get started with a non-profit website.

Next.js Non-Profit Website A non-profit website template powered by the Cosmic headless CMS. Uses Next.js, Tailwind CSS, and Stripe for donation payme

Cosmic 5 Sep 6, 2022
A bare-bones example Shopify app build with remix.run

Remix Shopify App A bare-bones Shopify app build with Remix Not supported by or affiliated with Shopify Create .env Add API_KEY to .env Add API_SECRET

Willson Smith 27 Jan 5, 2023
This is an app that will service the ASD community to help them communicate what they want. The individual can be either verbal or non-verbal.

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

MailingDelgadoMedina 3 Jun 28, 2022
Open Source and Embedded Nano Faucet

NanoDrop Open Source, Transparent and Embedded Nano Faucet Visit: https://nanodrop.io This project was created to help bring Nano to the masses. Fauce

Anarkrypto 30 Dec 26, 2022
A lightweight extension to automatically detect and provide verbose warnings for embedded iframe elements in order to protect against Browser-In-The-Browser (BITB) attacks.

Enhanced iFrame Protection - Browser Extension Enhanced iFrame Protection (EIP) is a lightweight extension to automatically detect and provide verbose

odacavo 16 Dec 24, 2022
This plugin can be embedded in PHP application to give the web application specific routes/href

Routes Plugin PHP This plugin can be embedded in PHP application to give the web application specific routes/href location and for entering specific/l

Ifechukwudeni Oweh 7 Jul 17, 2022
Embedded JavaScript templates for node

EJS Embedded JavaScript templates. NOTE: Version 2 of EJS makes some breaking changes with this version (notably, removal of the filters feature). Wor

TJ Holowaychuk 4.4k Dec 12, 2022
A Mail client embedded in Visual Studio Code.

VSCode Mail Client A Mail client embedded in Visual Studio Code Features Support IMAP and SMTP protocol. Gmail.com vendor test pass. 126.com vendor te

buhe 14 Nov 8, 2022
A handler to create embedded pages with buttons for pagination.

➝ Whats that β€’ A handler to create embedded pages with buttons for pagination. ➝ Requirements β€’ ["Handler"] pages.js β€’ [Example command] embed.js ➝ Us

null 8 Oct 30, 2022