Start implementing the Northfork technology

Getting started

There are a few different options to implementing the Northfork Technology. For a quick-and-easy implementation, you can utilize our powerful Widgets, where you basically need to add a few lines of code to your site to leverage the full power of the Northfork Technology. If you’d rather create your own look and feel, you can instead utilize our JS SDK or API to build whatever interface you think is right for your users.

Regardless of the approach you use, the Northfork team will always be close by and ready to support you throughout the implementation phase.

  1. Authentication

    Northfork provides an API key to be used when communicating with our services. This is sent through the X-Custom-Auth header when making requests to our API.

    curl -H 'X-Custom-Auth: {YOUR-API-KEY}'

API

This section shows a simple example of a user searching for recipes, resolving in-stock products for those recipes, and substituting a product for personal preference.

For more information, read our full API reference page here: https://developers.northfork.ai/reference

  1. Discover

    Get all recipes

    Let’s start by fetching all recipes.

    curl -X GET 'https://api.northfork.se/api/discovery/recipes' \
    -H 'X-Custom-Auth: {YOUR-API-KEY}'

    Find recipes that are “fully shopable”

    For us to only get recipes that have all ingredients currently in stock, for a specified store, we can use the store_identifier filter.

    curl -X GET 'https://api.northfork.se/api/discovery/recipes?store_identifier=100' \
    -H 'X-Custom-Auth: {YOUR-API-KEY}'

    Find vegetarian recipes

    Now, let’s add a filter for only fetching vegetarian recipes.

    curl -X GET 'https://api.northfork.se/api/discovery/recipes?store_identifier=100&diets=vegetarian' \
    -H 'X-Custom-Auth: {YOUR-API-KEY}'

    Find vegetarian recipes, with a maximum price per portion

    And with a maximum portion price.

    curl -X GET 'https://api.northfork.se/api/discovery/recipes?store_identifier=100&diets=vegetarian&max_portion_price=300' \
    -H 'X-Custom-Auth: {YOUR-API-KEY}'

SDK – JS

Northfork provides a JS SDK to make the implementation as smooth as possible, but with the power of UI/UX in the partner’s hand. This SDK also handles cart states and store lookup, which makes utilising the full Northfork potential a breeze. Basically everything you need to build a full recipe shopping experience for your users.

  1. Installation

    Before We get into Northfork SDK you need to have NodeJS installed on your computer
    this SDK supports both browsers as well as backend services that are running on NodeJS

    After you complete the installation of NodeJS you can start installing the NF SDK inside your project directory
    https://www.npmjs.com/package/northfork-sdk – Link to the NPM registry

  2. Getting Started

    Initialising the SDK

    No need to save the response from the initialise , SKD will save and authorise user by itself after calling initialize.

    import { initialize, getRecipe } from 'northfork-sdk'
    
    // Wait until initialisation will finish
    await initialize({ username: 'my-username', stage: 'development' })
    

    Find Recipes

    With the fetchRecipesFromDiscovery function you can fetch recipes using our Discovery API for Recipes, which allows you to use a whole set of different parameters to satisfy your needs.

    You can read more about how this api works on our reference page here.

    lets take a look at the example below

    // Find recipes
    const response = await sdk.fetchRecipesFromDiscovery({
      page,
      order_by,
      order_by_type,
    });
    
    return response;
    

    In the above example we are passing multiple parameters to the function.

    For the parameters that can be passed to the function, keep in mind that, only the page number is required.

    Default page/batch size of recipes fetched is 20 recipes.

    Default order_by value is ‘id’ and order_by_type value ‘desc’

    Discovery Recipe model

    type discoveryRecipeRequest = {
      page: number
      filter?: string
      tags?: string
      order_by?: string
      order_by_type?: string
      diet?: string
      allergies?: string
      min_cooking_time?: string
      max_cooking_time?: string
    }
    

    Get Recipe by Id

    To get a recipe you will have to pass the recipeID to the getRecipe method(Code example below) it will return a Promise

    note – this method excludes staples and shared ingredients

    // Get recipe by id
    const recipe = await getRecipe('recipe-id')
    
    console.log('Got recipe', recipe.title)
    

    Recipe model

    type RecipeModel = {
        id: string;
        title: string;
        products: Product[];
        portions: number;
        cookingTime: number;
        isBeingRemoved?: boolean;
    };
    

    Create Cart

    To Create a Cart you will need to pass the recipesIds to createCart method(Code example below) which will then return Promise

    // Create Cart
    const cart = await createCart(recipesIds: string[])
    
    console.log('Cart Created', cart)
    

    Cart model

    type RecipeModel = {
        id: string;
        title: string;
        products: Product[];
        portions: number;
        cookingTime: number;
        isBeingRemoved?: boolean;
    };
    
  3. Cart Actions

    Now that we have an understanding of basic SDK functions lets take a deep look at advance cart actions.
    Every CartAction returns a type CartActionResult which includes an object containing immediate cart state providing immediate update to a user and a promise with an eventual update from the API.

    type CartActionResult = {
        immeditateUpdate: CartModel;
        promise: Promise;
    };
    

    Toggle Product

    toggleProduct method can be used to include or exclude products from the cart, to use toggleProduct you will have to pass the cart object and the ID of the product that you want to include or exclude from the cart.

    toggleProduct: (cart: CartModel, productId: number) => CartActionResult;
    // Returns CartActionResult
    const updatedCart =  toggleProduct(cart, productId);
    
    console.log('Updated Cart', updatedCart.immeditateUpdate)
    
    console.log('Updated Cart from the API', await updatedCart.promise)
    

    Remove Recipe

    NF SDK supports multiple recipes when creating a cart, to remove recipes from the cart you can use removeRecipe method ,which will accept the cart object and the ID of the recipe which you wish to remove.

    removeRecipe: (cart: CartModel, recipeId: string) => CartActionResult;
    // Returns CartActionResult
    const updatedCart =  removeRecipe(cart, recipeId);
    
    console.log('Updated Cart', updatedCart.immeditateUpdate)
    
    console.log('Updated Cart from the API', await updatedCart.promise)

    Set Recipe Portions

    To change recipe portions you can use setPortions method, which accepts cart:CartModel , recipeId:string and the portions:number

    setPortions: (cart: CartModel, recipeId: string, portions: number) => CartActionResult;
    // Returns CartActionResult
    const updatedCart = setPortions(cart, recipeId, portions)
    
    console.log('Updated Cart', updatedCart.immeditateUpdate)
    
    console.log('Updated Cart from the API', await updatedCart.promise)

    Increment Product

    To increase quantity of a specific product incrementProduct method can be used.

    incrementProduct: (cart: CartModel, productId: number) => CartActionResult | undefined;
    // Returns CartActionResult
    const updatedCart = incrementProduct(cart, productId)
    
    console.log('Updated Cart', updatedCart.immeditateUpdate)
    
    console.log('Updated Cart from the API', await updatedCart.promise)

    Decrement Product

    To decrease quantity of a specific product decrementProduct method can be used.

    decrementProduct: (cart: CartModel, productId: number) => CartActionResult | undefined;
    // Returns CartActionResult
    const updatedCart = decrementProduct(cart, productId)
    
    console.log('Updated Cart', updatedCart.immeditateUpdate)
    
    console.log('Updated Cart from the API', await updatedCart.promise)

    Get Substitutes

    To get substitutes for a specific product you can use getSubstitutes method.

    Keep in mind that getSubstitutes method does not return a CartActionResult instead it will return a Promise<Product[]>

    getSubstitutes: (cart: CartModel, productId: number) => Promise<Product[]>;
    const substitutes = await getSubstitutes(cart, productId)
    
    console.log('Substitute products',substitutes)

    Swap Product

    To swap a product with one of its substitutes swapProduct method can be used.

    swapProduct: (cart: CartModel, currentProductId: number, newProduct: Product) => CartActionResult;
    // Returns CartActionResult
    const updatedCart = swapProduct(cart, productId, newProductId)
    
    console.log('Updated Cart', updatedCart.immeditateUpdate)
    
    console.log('Updated Cart from the API', await updatedCart.promise)
  4. Meal Planner

    Meal planner is another functionality that we offer on our NF SDK, using meal planner using our SDK is very simple and straightforward.

    Get Meal Planner

    Lets start with getMealPlanner method, this method allows you to create a meal planner and retrieve recipe groups:

    getMealPlanner: (mealPlannerRequest: MealPlannerRequest) => Promise<RecipeGroupResponse>;

    There are two data types that might confuse you when you take a look at this meal planner functionality but the data types are explained below:

    // Returns RecipeGroupResponse
    const recipeGroups = await getMealPlanner(mealPlannerRequest)
    
    console.log('recipeGroups',recipeGroups)
    
  5. Nutrition

    Using NF SDK you can get nutritional data to specific recipes of your choosing, to get nutritional data the existing getRecipe method can be used.

    Get Recipe Nutrition

    // Get recipe by id
    const recipe = await getRecipe('recipe-id')
    
    console.log('Got recipe', recipe.nutrition)
    
  6. Price Calculation

    With our built in getRecipePrices function you can calculate the total for a single recipe or multiple recipes.

    getRecipePrices: (recipePriceRequest: RecipePriceRequest) => Promise<Number>;

    Get Recipe Price

    const recipePrice = await sdk.getRecipePrices({
        storeId: storeId,
        recipes: [{ id: recipeId, portions: portions }],
      });
      
    console.log('Recipe Total',recipePrice)
    

    Note – the last two digits are cents ex:- 2356 => $23.56

SDK – Swift

Northfork Swift SDK

  1. Getting Started

    Initialising the SDK

    NorthforkSDK.shared.initialize(
      config: InitializationConfig(
        username: "abc", 
        stage: FlavorConfig.development)
      ) {
      isSucess in
      if (isSucess) {
        print("Success")
      } else {
        print("Failed")
      }
    } 
    

    Get Recipe

    // Get recipe by id
    NorthforkSDK.shared.getRecipe(recipeId: "recipe-id") {
      it in
      if let _recipe = it.data {
        // Data
      } else {
        print(it.error?.errorMessage ?? "")
      }
    }
    

    Create Cart

    // Create Cart
    NorthforkSDK.shared.createCart(cartRequest: CartCreationRequest(
      zip : "12345",
      storeId : "100",
      recipes : [RecipeRequest(id : "recipe-id", portions : 4)]
    )) {
      it in
      // Update cart model
      cart = it.data
      if let _cart = cart {
        print("Create cart: \(_cart.id) Success")
      } else {
        print(it.error?.errorMessage ?? "")
      } 
    }
    
  2. Cart Actions

    Remove Recipe

    NorthforkSDK.shared.removeRecipe(
      cart: cart!, 
      recipeId: "recipe-id"
    ) {
      newCart, response in
      isLoading = false
                
      // handle data
      guard response.error == nil
      else {
        resultText = response.error?.errorMessage ?? ""
        return
      }
      
      // Update new cart
      cart = newCart
    
      // Print data
      let data = response.data
      jsonEncoder.outputFormatting = .prettyPrinted
      let jsonData = try! jsonEncoder.encode(data)
      let json = String(data: jsonData, encoding: String.Encoding.utf8)
      resultText = json ?? ""
    }
    

    Set Recipe Portions

    
    NorthforkSDK.shared.setPortions(
      cart: cart!, 
      recipeId: "recipe-id", 
      portions: 4
    ) {
      newCart, response in
      isLoading = false
                
      // handle data
      guard response.error == nil
      else {
        resultText = response.error?.errorMessage ?? ""
        return
      }
      // Update new cart
      cart = newCart
      // Print data
      let data = response.data
      jsonEncoder.outputFormatting = .prettyPrinted
      let jsonData = try! jsonEncoder.encode(data)
      let json = String(data: jsonData, encoding: String.Encoding.utf8)
      resultText = json ?? ""
    }
    

    Increment Product

    
    NorthforkSDK.shared.incrementProduct(
      cart: cart!, 
      productId: 12345
    ) {
      newCart, response in
      isLoading = false
                
      // handle data
      guard response.error == nil
      else {
        resultText = response.error?.errorMessage ?? ""
        return
      }
      // Update new cart
      cart = newCart
                
      let data = response.data
      jsonEncoder.outputFormatting = .prettyPrinted
      let jsonData = try! jsonEncoder.encode(data)
      let json = String(data: jsonData, encoding: String.Encoding.utf8)
      resultText = json ?? ""
    }
    

    Decrement Product

    
    NorthforkSDK.shared.decrementProduct(
      cart: cart!, 
      productId: 12346
    ) {
      newCart, response in
      isLoading = false
                
      // handle data
      guard response.error == nil
      else {
        resultText = response.error?.errorMessage ?? ""
        return
      }
      // Update new cart
      cart = newCart
       
      let data = response.data
    }
    

    Get Substitutes

    To get substitutes for a specific product you can use getSubstitutes method.

    
    NorthforkSDK.shared.getSubstitutes(
      cart: cart!, 
    productId: 12345
    ) { 
      it in
      isLoading = false
      guard it.error == nil
      else {
        resultText = it.error?.errorMessage ?? ""
        return
      }
      let products = it.data
    }
    

SDK – Kotlin

Northfork Kotlin SDK

  1. Getting Started

    Initialising the SDK

    NorthforkSDK.initialize(this, InitializationConfig(username = "username", stage = FlavorConfig.DEVELOPMENT)) {  
        if (it) {  
          // initialize success  
        } 
    }

    Get Recipe

    // Get recipe by id
    NorthforkSDK.getRecipe(recipeId = "recipe1") { recipeResponse ->  
      recipeResponse.error?.let {  
        Toast.makeText(MainActivity@ this, it.errorMessage, Toast.LENGTH_SHORT).show()  
      }  
      recipeResponse.data?.let {  
                        
      }  
    }
    

    Create Cart

    // Create Cart
    NorthforkSDK.createCart(
          CartCreationRequest(zip = "12345",storeId = "100", recipes = listOf(RecipeRequest(id = "recipe1", portions = 4)))
     ) {
        it.error?.let {
                          
        }
     }
    
  2. Cart Actions

    Remove Recipe

    NorthforkSDK.removeRecipe(cart = cart!!, recipeId = "recipe1") { pair ->
      pair.second.data?.let {
        // update new cart
        cart = pair.first
        // print
        val jsonString = gson.toJson(it)
        tvResult.text = jsonString
      }
      pair.second.error?.let {
        tvResult.text = it.errorMessage
      }
    }
    

    Set Recipe Portions

    NorthforkSDK.setPortions(cart = cart!!, recipeId = "recipe1", portions = 4) { pair ->
      pair.second.data?.let {
        // update new cart
        cart = pair.first
        // print result
        val jsonString = gson.toJson(it)
        tvResult.text = jsonString
      }
      pair.second.error?.let {
        tvResult.text = it.errorMessage
      }
    }
    

    Increment Product

    NorthforkSDK.incrementProduct(cart = cart!!, productId = 12345) { 
      pair: Pair> ->
        if (pair.second.data != null) {
          // update new cart
          cart = pair.first
          // print
          val jsonString = gson.toJson(pair.second.data)
          tvResult.text = jsonString
        }
        pair.second.error?.let {
          tvResult.text = it.errorMessage
        }
    }
    

    Decrement Product

    NorthforkSDK.decrementProduct(cart = cart!!, productId = 12345) { 
      pair: Pair> ->
        if (pair.second.data != null) {
          // update new cart
          cart = pair.first
          // print
          val jsonString = gson.toJson(pair.second.data)
          tvResult.text = jsonString
        }
        pair.second.error?.let {
          tvResult.text = it.errorMessage
        }
    }
    

    Get Substitutes

    To get substitutes for a specific product you can use getSubstitutes method.

    
    NorthforkSDK.getSubstitutes(cart = cart!!, productId = 12345) {
      it.data?.let {
        val jsonString = gson.toJson(it)
        tvResult.text = jsonString
      }
      it.error?.let {
        tvResult.text = it.errorMessage
      }
    }
    

Web Widgets

Northfork widgets are a quick and easy way to start using the Northfork tools to enable recipe grocery shopping. The functionality is already built in with partners only required to add short javascript snippets to their web page.

 

Prerequisites

Markup for all widgets shall be placed where wanted rendered, with the exception being the Cart Widget.

 

Add Widgets library

<script id="nfw-v2" src=“https://widgets.northfork.se/dist/widgets.{user}.bundle.js”></script>

Add stylesheet

<link href=“http://widgets.northfork.se/dist/widgets.{user}.bundle.css” rel="stylesheet">
  1. Cart Widget

    Markup

    <div id=“nfw-cart-container” [data-options]></div>

    Support for multiple instantiations

    No

    Placement

    Before </body>

    Rendered

    Predetermined

    Data options

    Options may support multiple values, separated by comma. The order is nonsignificant. See the “Multi” column for reference.


  2. Meal Planner Widget

    Markup

    <div id=“nfw-menu-container” [data-options]></div>

    Support for multiple instantiations

    Yes

    Placement

    <body>

    Rendered

    Where placed

    Data options

    Options may support multiple values, separated by comma. The order is nonsignificant. See the “Multi” column for reference.

  3. Nutrition Widget

    Markup

    <div class=“nfw-anchor” data-widget=“nutrition-info” [data-options]></div>

    Support for multiple instantiations

    No

    Placement

    <body>

    Rendered

    Where placed

    Data options

    Options may support multiple values, separated by comma. The order is nonsignificant. See the “Multi” column for reference.