{"id":15641244,"url":"https://github.com/genaker/nodejento","last_synced_at":"2025-05-07T14:45:17.225Z","repository":{"id":49094989,"uuid":"311758088","full_name":"Genaker/nodejento","owner":"Genaker","description":"NodeJS implementation of the Magento 2 ORM data access layer without using legacy PHP","archived":false,"fork":false,"pushed_at":"2025-03-24T16:38:34.000Z","size":337,"stargazers_count":82,"open_issues_count":1,"forks_count":17,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-05-02T01:39:57.335Z","etag":null,"topics":["magento","magento2","microservices","mysql","nodejs","orm","sequelize"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Genaker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-10T19:05:31.000Z","updated_at":"2025-03-20T09:48:20.000Z","dependencies_parsed_at":"2024-04-03T01:41:15.247Z","dependency_job_id":null,"html_url":"https://github.com/Genaker/nodejento","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Genaker%2Fnodejento","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Genaker%2Fnodejento/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Genaker%2Fnodejento/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Genaker%2Fnodejento/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Genaker","download_url":"https://codeload.github.com/Genaker/nodejento/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252898134,"owners_count":21821560,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["magento","magento2","microservices","mysql","nodejs","orm","sequelize"],"created_at":"2024-10-03T11:41:57.698Z","updated_at":"2025-05-07T14:45:17.185Z","avatar_url":"https://github.com/Genaker.png","language":"JavaScript","readme":"# NodeJento (formerly known as NodeGento)\n\nNodeJS implementation of the Magento 2 ORM and Microservice Framework components without using legacy PHP.\n \nNodeJento is a NodeJs service providing an additional API surface that makes product, category, and any other data retrieval faster. \n\nDelivering great shopping experiences with Magento can be tricky, involving many factors. But two are undoubtedly part of the equation: Customers need to find what they’re looking for and they need to do it quickly. That’s why we developed NodeJento for Adobe Commerce.\n\nNodeJento is written in a highly scalable event-driven NodeJS/JavaScript. JavaScript is one of the most popular programming languages and nearly every developer is familiar with it.\n\nThis repo uses the Sequelize library to connect to the Magento 2 database directly without invocation of the Magento 2 PHP framework, so we won’t have to write any MYSQL queries.\n\n![Laragento](https://raw.githubusercontent.com/Genaker/nodegento/main/nodegento-logo.png)\n\nSequelize is a pretty great ORM. From their website:\n\n“Sequelize is a promise-based ORM for Node.js and io.js. It supports the dialects PostgreSQL, MySQL, MariaDB, SQLite and MSSQL and features solid transaction support, relations, read replication and more.”\n\nSequilize ORM is really popular and has 25K stars on GitHub:\n\n![Squilize ORM](https://user-images.githubusercontent.com/9213670/139718372-90124eeb-85bf-4b54-a556-aadf7895c765.png)\nSequilize has 1M+ weekly downloads:\n![Sequlize Downlods](https://user-images.githubusercontent.com/9213670/153321396-ce7126c4-546c-4237-b233-252f25367ba3.png)\n\nIn 2024 this number is 2M+ downloads and 28.9K stars:\n![image](https://github.com/Genaker/nodejento/assets/9213670/f84b77a4-95b4-44d1-8dbb-49d6f7f52082)\n\n# Installation\nGo to the magento root directory \n \t\n```\napt install npm #if not installed\nnpm install https://github.com/Genaker/nodejento/\nnode node_modules/nodejento/config-test.js\n```\nyou will see the results of DB connection array\n\nMake raw DB query with Knex:\n```\nnodejs\nconst DB = require('nodejento/config')\nlet connection = require('knex')({client: 'mysql', connection: DB.getDBConfig()});\nconnection.raw(\"select 1+1 as result\").then((e) =\u003e console.log(e))\nconnection.select('*').from('core_config_data').then((r) =\u003e console.log(r))\n```\nAll functions are async but with the console, it works ok ;)\n\n## Using Sequelize Laragento ORM Product model\n```\nconst { Sequelize } = require('sequelize');\nvar magentoModels = require(\"./Models/init-models\");\nconst sequelize = new Sequelize(\n    'magento',\n    'root',\n    'password',\n    {\n        host: '127.0.0.1',\n        dialect: 'mysql',\n\t//prevent sequelize from pluralizing table names\n        freezeTableName: true\n    });\n\nmagentoModels.CatalogProductEntity.findOne({ where: {'sku': '24-MB01'}}).then((p) =\u003e console.log(p.toJSON()));\n```\n# Concept\nModels are the essence of Sequelize. A model is an abstraction that represents a table in your Magento 2,1 database. In Sequelize, is a class that extends Model.\n\nThe model tells Sequelize several things about the entity it represents, such as the name of the table in the database and which columns it has (and their data types).\n\nA model in Sequelize has a name. This name does not have to be the same name as the table it represents in the Magento database. Usually, models have singular names (such as User) while tables have pluralized names (such as Users), although this is fully configurable.\n\nYou can simply tell Sequelize the name of the table directly as well.\n\n# Code Example\n\n```\nasync function getProduct(){\n\n// Get Product By SKU\nvar Product = await magentoModels.CatalogProductEntity.findOne({ where: {'sku': '24-MB01'}});\nconsole.log(Product);\n\n// get Product EAV Varchar attributes\nvar ProductEAV = await Product.getCatalogProductEntityVarchars();\n\nconsole.log(ProductEAV);\n\n// get Product with All EAV attributes\nProduct = await magentoModels.CatalogProductEntity.findOne({ where: {'sku': '24-MB01'},\ninclude: [\n          { model: magentoModels.CatalogProductEntityVarchar, as: 'CatalogProductEntityVarchars' },\n          { model: magentoModels.CatalogProductEntityInt, as: 'CatalogProductEntityInts' },\n          { model: magentoModels.CatalogProductEntityText, as: 'CatalogProductEntityTexts' },\n\t  { model: magentoModels.CatalogProductEntityDecimal, as: 'CatalogProductEntityDecimals'},\n\t  { model: magentoModels.CatalogProductEntityDatetime, as: 'CatalogProductEntityDatetimes'},\n        ]\n});\n\nconsole.log(Product);\n}\n```\n\n## Magento/Adobe Commerce edition Node JS Support \nIf you have any issues and Enterprise (Adobe Commerce) Version support create a ticket or drop me email at: yegorshytikov@gmail.com\n\n## Nodejento Express.JS Microservices  \nThe Magento less microservice can be built using two primary packages – Sequelize Magento ORM and Express or Fastify. \n\nThe Sequelize package connects microservices to the Magento MySQL Database directly using ORM models. The Express.js/Fastify is a web application server framework, designed for building web applications. It is the de facto standard server framework for Node.js.\n```\nconst express = require('express')\nconst { Sequelize } = require('sequelize');\nvar magentoModels = require(\"./Models/init-models\");\n\nconst app = express()\nconst port = 3000\nconst sequelize = new Sequelize(\n    'magento',\n    'root',\n    'password',\n    {\n        host: '127.0.0.1',\n        dialect: 'mysql',\n\t//prevent sequelize from pluralizing table names\n        freezeTableName: true\n    });\n\napp.get('/nodejento', async (req, res) =\u003e {\n  let Product = await magentoModels.CatalogProductEntity.findOne({ where: {'sku': '24-MB01'}});\n  res.send(Product.toJSON())\n})\n\napp.listen(port, () =\u003e {\n  console.log(`Magento Node JS microservice listening at http://localhost:${port}`)\n})\n```\n\n# Live Express server reloading \n\n**Nodemon** is a utility that will monitor for any changes in your source and automatically restart your server. Perfect for development.\n\nSwap nodemon instead of node to run your code, and now your process will automatically restart when your code changes. To install, get node.js, then from your terminal run:\n\n```\nnpm install nodemon --save\n```\nNow run **nodemon app.js** and you never have to restart again!\n\nIn the package.json you can use: \n\n```\nscripts:{\n\"start\":\"node app.js\",\n\"dev\": \"nodemon app.js\"\n}\n```\n\n# Sequilize Performance improvement\n\noptions.include.separate\tboolean\t\nIf true, runs a separate query to fetch the associated instances, only supported for hasMany associations.\n\nSequelize has a parameter called **separate**. Separate parameters were crucial in optimizing complex queries where you want to include associated nested data.\n\nIt’s only available for **hasMany** (Only HasMany associations support include.separate) associations, it takes those previously nested queries and performs them individually or separately using **WHERE IN([])** SQL condition. As a bonus, the results from each query are joined together later in memory, so we were able to maintain the same response and not have to alter how we were setting the data.\nWhat this meant for our situation: we were able to decouple our queries, perform them separately from one another and get a huge boost in efficiency. Measuring the before and after performance of a few endpoints, we estimated a 10x improvement. We were also able to target other queries with similar methods and associations and gain performance optimizations there as well.\n\nPrevious default joining approach takes: ORM: 57.209ms\nSeparate approach takes: ORM: 15.439ms\n\nFor the huge collection 2100 Products: before 12s after 1.029s\n\nResult SQL query will look like:\n```\nSELECT `value_id`, `store_id`, `value`, `attribute_id`, `entity_id` FROM `catalog_product_entity_varchar` AS `CatalogProductEntityVarchar` WHERE (`CatalogProductEntityVarchar`.`entity_id` IN (57, 58, 77, 89);\n```\n\n# Executing RAW SQL queries against Magento Database\n\nAs there are often use cases in which it is just easier to execute raw / already prepared SQL queries, you can use the sequelize.query method.\n\nBy default, the function will return two arguments - a results array, and an object containing metadata (such as amount of affected rows, etc). Note that since this is a raw query, the metadata are dialect-specific. Some dialects return the metadata \"within\" the results object (as properties on an array). However, two arguments will always be returned, but for MSSQL and MySQL it will be two references to the same object.\n\n```\nconst [results, metadata] = await sequelize.query(\"UPDATE users SET y = 42 WHERE x = 12\");\n// Results will be an empty array and metadata will contain the number of affected rows.\n```\n\nIn cases where you don't need to access the metadata, you can pass in a query type to tell sequelize how to format the results. For example, for a simple select query you could do:\n\n```\nconst { QueryTypes } = require('sequelize');\nconst users = await sequelize.query(\"SELECT * FROM `users`\", { type: QueryTypes.SELECT });\n// We didn't need to destructure the result here - the results were returned directly\n```\n\nSeveral other query types are available. Peek into the source for details.\n\nA second option is the model. If you pass a model the returned data will be instances of that model.\n\n```\n// Callee is the model definition. This allows you to easily map a query to a predefined model\nconst products = await sequelize.query('SELECT * FROM category_product_entity', {\n  model: Product\n});\n// Each element of `products` is now an instance of Product\n```\n\n# Eager Loading\n\nThe associated models will be added by Sequelize inappropriately named, automatically created field(s) in the returned objects.\n\nIn Sequelize, eager loading is mainly done by using the include option on a model finder query (such as findOne, findAll, etc).\n\n```\nconst products = await Product.findAll({ include: CatalogProductEntityVarchar, as: 'attribute'  });\nconsole.log(JSON.stringify(products));\n```\n\nAbove, the associated model was added to a new field called attribute in the fetched products.\n\nWhen you perform an **include** in a query, the included data will be added to an extra field in the returned objects, according to the following rules:\n\nWhen including something from a single association (hasOne or belongsTo) - the field name will be the singular version of the model name;\nWhen including something from a multiple association (hasMany or belongsToMany) - the field name will be the plural form of the model.\nIn short, the name of the field will take the most logical form in each situation.\n\nExamples:\n```\n// Assuming Foo.hasMany(Bar)\nconst foo = Foo.findOne({ include: Bar });\n// foo.bars will be an array\n// foo.bar will not exist since it doesn't make sense\n\n// Assuming Foo.hasOne(Bar)\nconst foo = Foo.findOne({ include: Bar });\n// foo.bar will be an object (possibly null if there is no associated model)\n// foo.bars will not exist since it doesn't make sense\n\n// And so on\n```\n\nOverriding singulars and plurals when defining aliases\nWhen defining an alias for an association, instead of using simply { as: 'myAlias' }, you can pass an object to specify the singular and plural forms:\n```\nProject.belongsToMany(User, {\n  as: {\n    singular: 'líder',\n    plural: 'líderes'\n  }\n});\n```\n\nIf you know that a model will always use the same alias in associations, you can provide the singular and plural forms directly to the model itself:\n```\nconst User = sequelize.define('user', { /* ... */ }, {\n  name: {\n    singular: 'líder',\n    plural: 'líderes',\n  }\n});\nProject.belongsToMany(User);\n```\n\n# Use together with the KNEX.JS\n\n\"Knex.js is a \"batteries included\" SQL query builder for Postgres, MSSQL, MySQL, MariaDB, SQLite3, Oracle, and Amazon Redshift designed to be flexible, portable, and fun to use. It features both traditional node style callbacks as well as a promise interface for cleaner async flow control, a stream interface, full-featured query and schema builders, transaction support (with savepoints), connection pooling and standardized responses between different query clients and dialects.\"\n\n```\nconst knex = require('knex')(dbConfig)\nknex('table').insert({a: 'b'}).returning('*').toString();\n\nknex({ a: 'table', b: 'table' })\n  .select({\n    aTitle: 'a.title',\n    bTitle: 'b.title'\n  })\n  .whereRaw('?? = ??', ['a.column_1', 'b.column_2'])\n  \nknex('users')\n  .where('id')\n  .first();\n  \nknex.column('entity_id', 'sku', 'created_at').select().from('catalog_product_entity');\n```\n# Use Magento NodeJS with AWS Lambda Serverless\n\nYou can use a Lambda function to process requests from an Application Load Balancer (ELB) and API Gateway \n\nElastic Load Balancing supports Lambda functions as a target for an Application Load Balancer. Use load balancer rules to route HTTP requests to a function, based on path or header values. Process the request and return an HTTP response from your Lambda function.\n\nElastic Load Balancing invokes your NodeJS Magento Lambda function synchronously with an event that contains the request body and metadata.\n\nExample Application Load Balancer request event\n```\n{\n    \"requestContext\": {\n        \"elb\": {\n            \"targetGroupArn\": \"arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a\"\n        }\n    },\n    \"httpMethod\": \"GET\",\n    \"path\": \"/lambda\",\n    \"queryStringParameters\": {\n        \"query\": \"1234ABCD\",\n\t\"sku\": \"24-MB01\"\n    },\n    \"headers\": {\n        \"accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\n        \"accept-encoding\": \"gzip\",\n        \"accept-language\": \"en-US,en;q=0.9\",\n        \"connection\": \"keep-alive\",\n        \"host\": \"lambda-alb-123578498.us-east-2.elb.amazonaws.com\",\n        \"upgrade-insecure-requests\": \"1\",\n        \"user-agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36\",\n        \"x-amzn-trace-id\": \"Root=1-5c536348-3d683b8b04734faae651f476\",\n        \"x-forwarded-for\": \"72.12.164.125\",\n        \"x-forwarded-port\": \"80\",\n        \"x-forwarded-proto\": \"http\",\n        \"x-imforwards\": \"20\"\n    },\n    \"body\": \"\",\n    \"isBase64Encoded\": false\n}\n```\n\n## Example of the Magento Lumbda with ELB or API Gataway:\n\n```\nconst { Sequelize } = require('sequelize');\nvar initModels = require(\"./Models/init-models\");\n\nconst sequelize = new Sequelize(\n    'magento',\n    'root',\n    '',\n    {\n        host: '127.0.0.1',\n        dialect: 'mysql',\n        logging: console.log,\n        freezeTableName: true\n    }\n);\n\nvar magentoModels = initModels(sequelize);\n\nexports.handler = async function (event, context) {\n\n    console.log(event);\n    // Get Product Record By SKU GET parameter\n    var Product = await magentoModels.CatalogProductEntity.findOne({ where: {'sku': event.queryStringParameters.sku}});\n    console.log(Product);\n\n    return {\n        \"isBase64Encoded\": false,\n        \"statusCode\": 200,\n        \"statusDescription\": \"200 OK\",\n        \"headers\": {\n            \"Content-Type\": \"application/json\"\n        },\n        \"body\": JSON.stringify(Product)\n    }\n}\n```\n\n# Run Magento with Express on AWS Lambda\n\nPreparing the Express app\nYour Express application no longer needs to listen on a TCP port – API Gateway will handle the web requests. Remove the usual call to app.listen, and just export the application from the module, so it can be used in a Lambda function.\n```\n// app.listen(3000) // \u003c-- find this line and delete it or comment it out\nmodule.exports = app; // add this line\n```\n\n\n![NodeJento2](https://raw.githubusercontent.com/Genaker/nodegento/main/nodegento-magento2.png)\n\n# Run Magento Microservice with Fastyfy \n\nWhy Fastyfy.\nAn efficient server implies a lower cost of the infrastructure, a better responsiveness under load and happy users. How can you efficiently handle the resources of your server, knowing that you are serving the highest number of requests possible, without sacrificing security validations and handy development?\n\nFastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town.\n\n```\n// Require the framework and instantiate it\nconst fastify = require('fastify')({ logger: true })\n\n// Declare a route\nfastify.get('/', async (request, reply) =\u003e {\n   // your logic here \n   reply.send('NodeJS with fastify.io')\n})\n\n// Run the server!\nconst start = async () =\u003e {\n  try {\n    await fastify.listen(3000)\n  } catch (err) {\n    fastify.log.error(err)\n    process.exit(1)\n  }\n}\nstart()\n```\n\n# Magento API using Next.JS amd NodeJento\n\nAPI routes provide a solution to build a public API with Next.js.\n\nAny file inside the folder **pages/api** is mapped to **/api/*** and will be treated as an API endpoint instead of a page. \u003cbr /\u003e\nOR you can create **pages/api/product/route.js** \u003cbr /\u003e\nA route file allows you to create custom request handlers for a given route. The following HTTP methods are supported: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS.\n\n\n## Lets Create Product Data API \nCreate file **pages/api/product.js**\n```\nconst initModels = require(\"./Models/init-models\");\n// Magento DB connection here \nconconst conection = require(\"../connection\");\n\nlet allMagentoModels = initModels(connection);\nlet {catalogProductEntity} = allMagentoModels;\n\nconst handler = async (req, res) =\u003e {\n  try {\n      if( req.method === \"GET\") {\n        var Product = await catalogProductEntity.findOne({ where: {'sku': req.query.sku}});\n        res.status(200).json({product: Product});\n      } else {\n\tres.setHeader(\"Allow\", [\"GET\"]);\n        res.status(405).end(`Method ${method} Not Allowed With NextJS Magento Product API`);\n    }\n  } catch (err) {\n    res.status(400).json({\n      error_code: \"product_api_error\",\n      message: err.message,\n    });\n  }\n};\nexport default handler;\n\n```\nNow You have Product API at http://localhost:3000/api/product?sku=testSku\nThe next step is to move it to **pages/API/product/[sku].js** to remove query parameters. \n\n\n# Magento microservices using Metarhia Stack\n\nServer init file: server.js\n\n```\n'use strict';\n\nrequire('impress');\n```\n\nAPI endpoint example: application/api/nodejento/example.js\n```\nasync () =\u003e {\n  return { result: 'success', data };\n};\n```\n\n## GraphQL support\nUse graphql-sequelize Resolve helpers\n```\nimport { resolver } from \"graphql-sequelize\";\n\nresolver(SequelizeModel[, options]);\n\n```\nA helper for resolving GraphQL queries targeted at Magento Sequelize models or associations. \nPlease take a look at the documentation to best get an idea of implementation: https://github.com/mickhansen/graphql-sequelize\n\n# Fetch Magento app/etc/env.php config as a JSON \n\nTo make it simple, we configure it to JSON and use it, and read config.json from the original config\n\n```\nphp -r '$x = include(\"app/etc/env.php\"); echo json_encode($x);' \u003e config.json\n```\n\nNow we can use the magento env.php configuration file to fetch database credentials.\n\nExample: \n\n```\nconst magentoConfig = require('./config.js');\nmagentoConfig.BP = \"/var/www/html/magento/\";\nmagentoConfig.getBasePath();\nmagentoConfig.getDBConfig().then((p)=\u003e console.log(p));\n\n```\n\nResult: \n\n![image](https://user-images.githubusercontent.com/9213670/153312851-9c95e513-c403-4ed1-9662-0720a90b91e9.png)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgenaker%2Fnodejento","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgenaker%2Fnodejento","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgenaker%2Fnodejento/lists"}