├── src ├── assets │ └── sass │ │ ├── _general.scss │ │ ├── _mixines.sass │ │ ├── _common.sass │ │ └── _variables.scss ├── components │ ├── App │ │ ├── _styles.scss │ │ └── index.jsx │ └── index.js ├── services │ ├── index.js │ └── requestAPI.js ├── index.html └── index.js ├── .gitignore ├── server ├── server.es.schema.js ├── server.client.js ├── server.es.delete.js ├── server.es.create.js ├── server.es.bulk.js ├── server.elasticsearch.js ├── server.graphql.js ├── server.js └── json │ ├── es.settings-mappings.json │ └── books.json ├── .babelrc ├── README.md ├── .eslintrc.json ├── webpack.config.js └── package.json /src/assets/sass/_general.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/sass/_mixines.sass: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/App/_styles.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export * from "./App"; -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | export * from "./requestAPI"; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | /node_modules 4 | /*.lock 5 | *.lock 6 | /*.http 7 | *.http 8 | -------------------------------------------------------------------------------- /server/server.es.schema.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "size": 1000, 3 | "from": 0, 4 | "query": { 5 | "match_all": {} 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/assets/sass/_common.sass: -------------------------------------------------------------------------------- 1 | //@import "~bootstrap/scss/bootstrap-reboot" 2 | //@import "~bootstrap/scss/bootstrap-grid" 3 | @import "~bootstrap/scss/bootstrap" 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | ["@babel/plugin-proposal-class-properties", { "loose": true }], 8 | ] 9 | } -------------------------------------------------------------------------------- /server/server.client.js: -------------------------------------------------------------------------------- 1 | const ElasticSearch = require('elasticsearch'); 2 | 3 | /** 4 | * *** ElasticSearch *** client 5 | * @type {Client} 6 | */ 7 | const client = new ElasticSearch.Client({ 8 | hosts: ['http://127.0.0.1:9200'] 9 | }); 10 | 11 | module.exports = client; 12 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /server/server.es.delete.js: -------------------------------------------------------------------------------- 1 | const client = require('./server.client'); 2 | 3 | client.indices.delete( 4 | { 5 | index: "catalog" 6 | }, 7 | (error, response, status) => { 8 | if(!error) { 9 | console.info("🚀 Deleted index"); 10 | console.info(response); 11 | } else { 12 | console.info(error); 13 | } 14 | 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 ElasticSearch, GraphQL, React, Node+Express🤯! How ? 2 | Simple application using React+GraphQL, ElasticSearch as full-text search and Node JS 3 | [medium.com/@andrewrymaruk](https://medium.com/@andrewrymaruk/elasticsearch-graphql-react-node-express-how-cb2c2cc708f8) 4 | 5 | ![Simple application using React+GraphQL, ElasticSearch as full-text search and Node JS](https://miro.medium.com/max/1600/0*CkvXp7vOOVgcGt6A.gif) 6 | -------------------------------------------------------------------------------- /server/server.es.create.js: -------------------------------------------------------------------------------- 1 | const client = require('./server.client'); 2 | const params = require('./json/es.settings-mappings'); 3 | 4 | client.indices.create( 5 | { 6 | index: "catalog", 7 | body: params 8 | }, 9 | (error, response, status) => { 10 | if(!error) { 11 | console.info("\n🚀 Created a new index"); 12 | console.info(response); 13 | console.info('\n'); 14 | } else { 15 | console.info(error); 16 | } 17 | 18 | } 19 | ); 20 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true 7 | } 8 | }, 9 | "parser": "babel-eslint", 10 | "plugins": [ 11 | "react-hooks" 12 | ], 13 | "rules": { 14 | "semi": "error", 15 | "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks 16 | "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies 17 | } 18 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./components"; 4 | 5 | import { ApolloProvider } from 'react-apollo' 6 | import { ApolloClient } from 'apollo-client' 7 | import { createHttpLink } from 'apollo-link-http' 8 | import { InMemoryCache } from 'apollo-cache-inmemory' 9 | 10 | const httpLink = createHttpLink({ 11 | uri: 'http://localhost:9100/graphql' 12 | }); 13 | 14 | const client = new ApolloClient({ 15 | link: httpLink, 16 | cache: new InMemoryCache() 17 | }); 18 | 19 | ReactDOM.render( 20 | , 21 | document.getElementById("root") 22 | ); 23 | -------------------------------------------------------------------------------- /src/services/requestAPI.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const AxiosInstanse = axios.create({ 4 | baseURL: 'http://localhost:9100/api', 5 | proxyHeaders: false, 6 | credentials: true, 7 | crossdomain: true, 8 | headers: { 9 | 'Content-Type': 'application/json' 10 | }, 11 | }); 12 | 13 | const AxiosClient = { 14 | method: null, 15 | url: null, 16 | data: {} 17 | }; 18 | 19 | export const requestAPI = args => ( 20 | new Promise((resolve, reject) => { 21 | AxiosClient = {...AxiosClient, ...args}; 22 | AxiosClient.data = args.body; 23 | AxiosInstanse.request(AxiosClient) 24 | .then(r => resolve(r)) 25 | .catch(e => reject(e)); 26 | }) 27 | ); 28 | -------------------------------------------------------------------------------- /server/server.es.bulk.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const client = require('./server.client'); 3 | const PRODUCTS = require('./json/books.json').products; 4 | 5 | /** 6 | * Generate own bulk schema: 7 | * {index: {_index: "catalog"}} 8 | * {name: "Some name of product", brand: "Name Brand", ...} 9 | */ 10 | let initialBulk = {index: {_index: "catalog"}}; 11 | let collectionBulk = []; 12 | _.map(_.keys(PRODUCTS), uuid => { 13 | collectionBulk = [ 14 | ...collectionBulk, 15 | initialBulk, 16 | PRODUCTS[uuid] 17 | ]; 18 | }); 19 | 20 | client.bulk({body: collectionBulk}, function (err, r) { 21 | if (err) { 22 | console.log(`Failed Bulk operation\n`, err); 23 | } else { 24 | console.log(`🚀 Successfully imported ${_.keys(PRODUCTS).length} items \n`); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /src/assets/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | $blue: #027bf3; 2 | $blue-hover: #028aff; 3 | $blue-d: #0463c2; 4 | $red: #e12828; 5 | $green: #30dc45; 6 | $white: #fff; 7 | $gray-vl:#fafafa; 8 | $gray-l:#f9f9f9; 9 | $gray-m:#f1f1f1; 10 | $gray-d:#ebebeb; 11 | $radius:2px; 12 | 13 | $color_panel :rgba(49, 49, 49, 0.94); 14 | $color_logo :rgba(0, 0, 0, 0.8); 15 | $color_bg :#F2F2F2; 16 | $color_orange :#ff7a43; 17 | $green :#2eab81; 18 | $green_dark :#22946e; 19 | $green_shadow :#24946f; 20 | $green_light :#30cf9a; 21 | $gray :#e2e2e2; 22 | $gray_shadow :#bebebe; 23 | $gray_light :#e9e9e9; 24 | $blue :#568fde; 25 | $blue_dark :#275fad; 26 | $blue_shadow :#275fad; 27 | $blue_light :#6cacff; 28 | $violet :#7786ff; 29 | $red_light : #ee4141; 30 | $red_error: #ff9ba2; 31 | $shadow: 0px 2px 0px; 32 | $shadow-btn: 1px 0px 0px; 33 | -------------------------------------------------------------------------------- /server/server.elasticsearch.js: -------------------------------------------------------------------------------- 1 | const client = require('./server.client'); 2 | const elasticSearchSchema = require('./server.es.schema'); 3 | 4 | /** 5 | * TODO Ping the CLIENT to be sure 6 | * *** ElasticSearch *** is up 7 | */ 8 | client.ping({ 9 | requestTimeout: 30000, 10 | }, function (error) { 11 | error 12 | ? console.error('ElasticSearch cluster is down!') 13 | : console.log('ElasticSearch is ok'); 14 | }); 15 | 16 | function ElasticSearchClient(body) { 17 | // perform the actual search passing in the index, the search query and the type 18 | return client.search({index: 'catalog', body: body}); 19 | } 20 | 21 | function ApiElasticSearchClient(req, res) { 22 | // perform the actual search passing in the index, the search query and the type 23 | ElasticSearchClient({...elasticSearchSchema}) 24 | .then(r => res.send(r['hits']['hits'])) 25 | .catch(e => { 26 | console.error(e); 27 | res.send([]); 28 | }); 29 | } 30 | 31 | module.exports = { 32 | ApiElasticSearchClient, 33 | ElasticSearchClient 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/App/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Query } from 'react-apollo'; 3 | import gql from 'graphql-tag'; 4 | import * as _ from "lodash"; 5 | 6 | const PRODUCTS = gql`{ 7 | products { 8 | } 9 | }`; 10 | 11 | export class App extends Component { 12 | render() { 13 | return ( 14 | 15 | { 16 | ({ loading, error, data }) => ( 17 | loading 18 | ? (<>Loading...) 19 | : ( 20 | 31 | ) 32 | ) 33 | } 34 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/server.graphql.js: -------------------------------------------------------------------------------- 1 | const {ElasticSearchClient} = require('./server.elasticsearch'); 2 | const elasticSearchSchema = require('./server.es.schema'); 3 | const {makeExecutableSchema} = require('graphql-tools'); 4 | 5 | // Construct a schema, using GraphQL schema language 6 | const typeDefs = ` 7 | type inOthers { 8 | name: String! 9 | quantity: String! 10 | uuid: String! 11 | } 12 | 13 | type inStocks { 14 | name: String! 15 | quantity: String! 16 | uuid: String! 17 | } 18 | 19 | type Product { 20 | name: String 21 | default_image: String 22 | new_product: Boolean 23 | brand: String! 24 | promos: [String] 25 | article: String 26 | promotion: Boolean 27 | price: String 28 | in_stocks: [inStocks] 29 | in_others: inOthers 30 | in_waiting: inOthers 31 | currency_name: String 32 | } 33 | 34 | type Query { 35 | products: [Product] 36 | } 37 | `; 38 | 39 | // The root provides a resolver function for each API endpoint 40 | const resolvers = { 41 | Query: { 42 | products: () => new Promise((resolve, reject) => { 43 | ElasticSearchClient({...elasticSearchSchema}) 44 | .then(r => { 45 | let _source = r['hits']['hits']; 46 | _source.map((item, i) => _source[i] = item._source); 47 | 48 | resolve(_source); 49 | }); 50 | }), 51 | } 52 | }; 53 | 54 | module.exports = makeExecutableSchema({ 55 | "typeDefs": [typeDefs], 56 | "resolvers": resolvers 57 | }); 58 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const bodyParser = require('body-parser'); 4 | const path = require('path'); 5 | const {ApolloServer} = require('apollo-server-express'); 6 | const {ApiElasticSearchClient} = require('./server.elasticsearch'); 7 | const madeExecutableSchema = require('./server.graphql'); 8 | 9 | // PORT 10 | const PORT = 9100; 11 | 12 | const server = new ApolloServer({ 13 | schema: madeExecutableSchema, 14 | playground: true, 15 | }); 16 | 17 | // TODO Use the BodyParser as a middleware 18 | app.use(bodyParser.json()); 19 | 20 | // TODO Set port for the app to listen on 21 | app.set('port', process.env.PORT || 3001); 22 | 23 | // TODO Set path to serve static files 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | // TODO Enable CORS 27 | app.use(function (req, res, next) { 28 | res.header("Access-Control-Allow-Origin", "*"); 29 | res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); 30 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Access-Control-Request-Headers, Access-Control-Request-Method"); 31 | next(); 32 | }); 33 | 34 | // Define the `/search` route that should return elastic search results 35 | app.get('/search', ApiElasticSearchClient); 36 | 37 | server.applyMiddleware({app}); 38 | 39 | app.listen(PORT, function () { 40 | console.log(`Express server listening on port :${PORT}${server.graphqlPath}`); 41 | }); 42 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 4 | const webpack = require("webpack"); 5 | const glob = require("glob"); 6 | 7 | // const devMode = process.env.NODE_ENV !== 'production'; 8 | 9 | module.exports = { 10 | 11 | entry: { 12 | js: [ 13 | "@babel/polyfill", 14 | path.resolve(__dirname, "./src/index.js"), 15 | ...glob.sync("./src/**/*.s*ss") 16 | ] 17 | }, 18 | 19 | output: { 20 | path: path.join(__dirname, "/dist"), 21 | publicPath: "/", 22 | filename: "js/[name].bundle.js" 23 | }, 24 | 25 | module: { 26 | rules: [ 27 | 28 | { 29 | test: /\.(js|jsx)$/, 30 | exclude: /node_modules/, 31 | use: ["babel-loader"] 32 | }, 33 | 34 | { 35 | test: /\.(sass|scss)$/, 36 | exclude: /node_modules/, 37 | use: ExtractTextPlugin.extract({ 38 | fallback: 'style-loader', 39 | use: [ 40 | // {loader: "react-web-component-style-loader"}, 41 | {loader: 'css-loader'}, 42 | { 43 | loader: 'postcss-loader', 44 | options: { 45 | plugins: loader => [ 46 | require('autoprefixer'), 47 | ] 48 | } 49 | }, 50 | {loader: 'sass-loader'}] 51 | }) 52 | }, 53 | 54 | { 55 | test: /\.css$/, 56 | use: [ 57 | {loader: "react-web-component-style-loader"}, 58 | {loader: "css-loader"} 59 | ] 60 | }, 61 | 62 | ] 63 | }, 64 | 65 | resolve: { 66 | extensions: ['.js', '.jsx'], 67 | }, 68 | 69 | plugins: [ 70 | new webpack.HotModuleReplacementPlugin(), 71 | 72 | new HtmlWebpackPlugin(), 73 | 74 | new ExtractTextPlugin({ 75 | filename: 'css/styles.css' 76 | }), 77 | 78 | new HtmlWebpackPlugin({ 79 | template: "./src/index.html", 80 | filename: "index.html", 81 | path: path.resolve(__dirname, '') 82 | }), 83 | 84 | ], 85 | 86 | devServer: { 87 | publicPath: '/', 88 | historyApiFallback: true, 89 | inline: true, 90 | port: 9999 91 | }, 92 | }; 93 | -------------------------------------------------------------------------------- /server/json/es.settings-mappings.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "number_of_shards" : 1, 4 | "max_ngram_diff": 99, 5 | "max_shingle_diff": 99, 6 | "analysis": { 7 | "analyzer": { 8 | "brand_analyzer": { 9 | "tokenizer": "whitespace", 10 | "filter": [ 11 | "lowercase", 12 | "trim" 13 | ] 14 | }, 15 | "catalog_analyzer": { 16 | "tokenizer": "whitespace", 17 | "filter": [ 18 | "lowercase" 19 | ] 20 | }, 21 | "article_analyzer": { 22 | "tokenizer": "articule_tokenizer", 23 | "filter": [ 24 | "lowercase" 25 | ] 26 | } 27 | }, 28 | "tokenizer": { 29 | "catalog_tokenizer": { 30 | "type": "ngram", 31 | "min_gram": 4, 32 | "max_gram": 32, 33 | "token_chars": [ 34 | "letter", 35 | "digit" 36 | ] 37 | }, 38 | "articule_tokenizer": { 39 | "type": "ngram", 40 | "min_gram": 3, 41 | "max_gram": 24 42 | }, 43 | "brand_tokenizer": { 44 | "type": "ngram", 45 | "min_gram": 2, 46 | "max_gram": 12, 47 | "token_chars": [ 48 | "letter", 49 | "digit" 50 | ] 51 | }, 52 | "nodes_tokenizer": { 53 | "type": "path_hierarchy", 54 | "delimiter": "\\", 55 | "replacement": "/" 56 | }, 57 | "path_tokenizer": { 58 | "type": "path_hierarchy", 59 | "delimiter": "\\", 60 | "replacement": "/" 61 | } 62 | } 63 | } 64 | }, 65 | "mappings": { 66 | "properties": { 67 | "uuid": { 68 | "type": "text" 69 | }, 70 | "article": { 71 | "type": "text", 72 | "analyzer": "article_analyzer", 73 | "fielddata": true, 74 | "fields": { 75 | "raw": { 76 | "type": "keyword" 77 | } 78 | } 79 | }, 80 | "brand": { 81 | "type": "text", 82 | "fielddata": true, 83 | "analyzer": "brand_analyzer", 84 | "fields": { 85 | "raw": { 86 | "type": "keyword" 87 | } 88 | } 89 | }, 90 | "name": { 91 | "type": "text", 92 | "fielddata": true, 93 | "analyzer": "catalog_analyzer", 94 | "fields": { 95 | "raw": { 96 | "type": "keyword" 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-webpack", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "eslintConfig": ".eslintrc.json", 7 | "scripts": { 8 | "start": "webpack-dev-server --mode development --open --hot", 9 | "build": "webpack --config webpack.config.js --mode production", 10 | "storybook": "start-storybook -p 9001 -c .storybook", 11 | "sb": "start-storybook -p 9001 -c .storybook", 12 | "es:create": "node ./server/server.es.create.js && node ./server/server.es.bulk.js", 13 | "es:delete": "node ./server/server.es.delete.js" 14 | }, 15 | "postcss": {}, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "devDependencies": { 20 | "@babel/core": "^7.5.5", 21 | "@babel/plugin-proposal-class-properties": "^7.5.5", 22 | "@babel/polyfill": "^7.4.4", 23 | "@babel/preset-env": "^7.5.5", 24 | "@babel/preset-react": "^7.0.0", 25 | "@sambego/storybook-styles": "^1.0.0", 26 | "@storybook/addon-actions": "^5.1.10", 27 | "@storybook/addon-console": "^1.2.1", 28 | "@storybook/addon-info": "^5.1.10", 29 | "@storybook/addon-knobs": "^5.1.10", 30 | "@storybook/addon-links": "^5.1.10", 31 | "@storybook/addon-notes": "^5.1.10", 32 | "@storybook/addon-options": "^5.1.10", 33 | "@storybook/addon-storysource": "^5.1.10", 34 | "@storybook/addons": "^5.1.10", 35 | "@storybook/react": "^5.1.10", 36 | "apollo-boost": "^0.4.3", 37 | "apollo-server-express": "^2.8.1", 38 | "autoprefixer": "^9.6.1", 39 | "axios": "^0.19.0", 40 | "babel-eslint": "^10.0.3", 41 | "babel-loader": "^8.0.0-beta.6", 42 | "babel-plugin-react-hooks-hot-load": "^1.2.0", 43 | "babel-polyfill": "^6.26.0", 44 | "babel-preset-es2015": "^6.24.1", 45 | "body-parser": "^1.19.0", 46 | "classnames": "^2.2.6", 47 | "clean-webpack-plugin": "^3.0.0", 48 | "cors": "^2.8.5", 49 | "cross-env": "^5.2.0", 50 | "css-loader": "^3.2.0", 51 | "elasticsearch": "^16.3.0", 52 | "eslint": "^6.1.0", 53 | "eslint-config-airbnb": "^17.1.1", 54 | "eslint-plugin-react-hooks": "^1.6.1", 55 | "express": "^4.17.1", 56 | "express-graphql": "^0.9.0", 57 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 58 | "file-loader": "^4.2.0", 59 | "fs": "^0.0.1-security", 60 | "glob": "^7.1.4", 61 | "graphql": "^14.4.2", 62 | "graphql-tag": "^2.10.1", 63 | "graphql-tools": "^4.0.5", 64 | "html-webpack-plugin": "^3.2.0", 65 | "image-webpack-loader": "^5.0.0", 66 | "metro-babel7-plugin-react-transform": "^0.54.1", 67 | "node-sass": "^4.12.0", 68 | "path": "^0.12.7", 69 | "postcss-loader": "^3.0.0", 70 | "postcss-smart-import": "^0.7.6", 71 | "prop-types": "^15.6.2", 72 | "raw-loader": "^3.1.0", 73 | "react-apollo": "^3.0.0", 74 | "react-highlight": "^0.12.0", 75 | "react-redux": "^7.1.0", 76 | "react-script": "^2.0.5", 77 | "react-web-component": "^1.3.0", 78 | "react-web-component-style-loader": "^0.1.4-alpha", 79 | "redux": "^4.0.4", 80 | "request": "^2.88.0", 81 | "sass-loader": "^7.1.0", 82 | "storybook-addon-code": "^0.1.4", 83 | "storybook-addon-jsx": "^7.1.5", 84 | "storybook-readme": "^5.0.6", 85 | "style-loader": "^1.0.0", 86 | "webpack": "^4.39.1", 87 | "webpack-cli": "^3.3.6", 88 | "webpack-dev-server": "^3.7.2" 89 | }, 90 | "dependencies": { 91 | "@webcomponents/webcomponentsjs": "^2.2.10", 92 | "bootstrap": "^4.2.1", 93 | "jquery": "^3.4.1", 94 | "lodash": "^4.17.15", 95 | "react": "^16.9.0-rc.0", 96 | "react-dom": "^16.9.0-rc.0", 97 | "react-hot-loader": "^4.12.10" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /server/json/books.json: -------------------------------------------------------------------------------- 1 | { 2 | "products": [ 3 | { 4 | "brand": "Chinua Achebe", 5 | "country": "Nigeria", 6 | "imageLink": "images/things-fall-apart.jpg", 7 | "language": "English", 8 | "link": "https://en.wikipedia.org/wiki/Things_Fall_Apart\n", 9 | "pages": 209, 10 | "name": "Things Fall Apart", 11 | "article": 1958 12 | }, 13 | { 14 | "brand": "Hans Christian Andersen", 15 | "country": "Denmark", 16 | "imageLink": "images/fairy-tales.jpg", 17 | "language": "Danish", 18 | "link": "https://en.wikipedia.org/wiki/Fairy_Tales_Told_for_Children._First_Collection.\n", 19 | "pages": 784, 20 | "name": "Fairy tales", 21 | "article": 1836 22 | }, 23 | { 24 | "brand": "Dante Alighieri", 25 | "country": "Italy", 26 | "imageLink": "images/the-divine-comedy.jpg", 27 | "language": "Italian", 28 | "link": "https://en.wikipedia.org/wiki/Divine_Comedy\n", 29 | "pages": 928, 30 | "name": "The Divine Comedy", 31 | "article": 1315 32 | }, 33 | { 34 | "brand": "Unknown", 35 | "country": "Sumer and Akkadian Empire", 36 | "imageLink": "images/the-epic-of-gilgamesh.jpg", 37 | "language": "Akkadian", 38 | "link": "https://en.wikipedia.org/wiki/Epic_of_Gilgamesh\n", 39 | "pages": 160, 40 | "name": "The Epic Of Gilgamesh", 41 | "article": -1700 42 | }, 43 | { 44 | "brand": "Unknown", 45 | "country": "Achaemenid Empire", 46 | "imageLink": "images/the-book-of-job.jpg", 47 | "language": "Hebrew", 48 | "link": "https://en.wikipedia.org/wiki/Book_of_Job\n", 49 | "pages": 176, 50 | "name": "The Book Of Job", 51 | "article": -600 52 | }, 53 | { 54 | "brand": "Unknown", 55 | "country": "India/Iran/Iraq/Egypt/Tajikistan", 56 | "imageLink": "images/one-thousand-and-one-nights.jpg", 57 | "language": "Arabic", 58 | "link": "https://en.wikipedia.org/wiki/One_Thousand_and_One_Nights\n", 59 | "pages": 288, 60 | "name": "One Thousand and One Nights", 61 | "article": 1200 62 | }, 63 | { 64 | "brand": "Unknown", 65 | "country": "Iceland", 66 | "imageLink": "images/njals-saga.jpg", 67 | "language": "Old Norse", 68 | "link": "https://en.wikipedia.org/wiki/Nj%C3%A1ls_saga\n", 69 | "pages": 384, 70 | "name": "Nj\u00e1l's Saga", 71 | "article": 1350 72 | }, 73 | { 74 | "brand": "Jane Austen", 75 | "country": "United Kingdom", 76 | "imageLink": "images/pride-and-prejudice.jpg", 77 | "language": "English", 78 | "link": "https://en.wikipedia.org/wiki/Pride_and_Prejudice\n", 79 | "pages": 226, 80 | "name": "Pride and Prejudice", 81 | "article": 1813 82 | }, 83 | { 84 | "brand": "Honor\u00e9 de Balzac", 85 | "country": "France", 86 | "imageLink": "images/le-pere-goriot.jpg", 87 | "language": "French", 88 | "link": "https://en.wikipedia.org/wiki/Le_P%C3%A8re_Goriot\n", 89 | "pages": 443, 90 | "name": "Le P\u00e8re Goriot", 91 | "article": 1835 92 | }, 93 | { 94 | "brand": "Samuel Beckett", 95 | "country": "Republic of Ireland", 96 | "imageLink": "images/molloy-malone-dies-the-unnamable.jpg", 97 | "language": "French, English", 98 | "link": "https://en.wikipedia.org/wiki/Molloy_(novel)\n", 99 | "pages": 256, 100 | "name": "Molloy, Malone Dies, The Unnamable, the trilogy", 101 | "article": 1952 102 | }, 103 | { 104 | "brand": "Giovanni Boccaccio", 105 | "country": "Italy", 106 | "imageLink": "images/the-decameron.jpg", 107 | "language": "Italian", 108 | "link": "https://en.wikipedia.org/wiki/The_Decameron\n", 109 | "pages": 1024, 110 | "name": "The Decameron", 111 | "article": 1351 112 | }, 113 | { 114 | "brand": "Jorge Luis Borges", 115 | "country": "Argentina", 116 | "imageLink": "images/ficciones.jpg", 117 | "language": "Spanish", 118 | "link": "https://en.wikipedia.org/wiki/Ficciones\n", 119 | "pages": 224, 120 | "name": "Ficciones", 121 | "article": 1965 122 | }, 123 | { 124 | "brand": "Emily Bront\u00eb", 125 | "country": "United Kingdom", 126 | "imageLink": "images/wuthering-heights.jpg", 127 | "language": "English", 128 | "link": "https://en.wikipedia.org/wiki/Wuthering_Heights\n", 129 | "pages": 342, 130 | "name": "Wuthering Heights", 131 | "article": 1847 132 | }, 133 | { 134 | "brand": "Albert Camus", 135 | "country": "Algeria, French Empire", 136 | "imageLink": "images/l-etranger.jpg", 137 | "language": "French", 138 | "link": "https://en.wikipedia.org/wiki/The_Stranger_(novel)\n", 139 | "pages": 185, 140 | "name": "The Stranger", 141 | "article": 1942 142 | }, 143 | { 144 | "brand": "Paul Celan", 145 | "country": "Romania, France", 146 | "imageLink": "images/poems-paul-celan.jpg", 147 | "language": "German", 148 | "link": "\n", 149 | "pages": 320, 150 | "name": "Poems", 151 | "article": 1952 152 | }, 153 | { 154 | "brand": "Louis-Ferdinand C\u00e9line", 155 | "country": "France", 156 | "imageLink": "images/voyage-au-bout-de-la-nuit.jpg", 157 | "language": "French", 158 | "link": "https://en.wikipedia.org/wiki/Journey_to_the_End_of_the_Night\n", 159 | "pages": 505, 160 | "name": "Journey to the End of the Night", 161 | "article": 1932 162 | }, 163 | { 164 | "brand": "Miguel de Cervantes", 165 | "country": "Spain", 166 | "imageLink": "images/don-quijote-de-la-mancha.jpg", 167 | "language": "Spanish", 168 | "link": "https://en.wikipedia.org/wiki/Don_Quixote\n", 169 | "pages": 1056, 170 | "name": "Don Quijote De La Mancha", 171 | "article": 1610 172 | }, 173 | { 174 | "brand": "Geoffrey Chaucer", 175 | "country": "England", 176 | "imageLink": "images/the-canterbury-tales.jpg", 177 | "language": "English", 178 | "link": "https://en.wikipedia.org/wiki/The_Canterbury_Tales\n", 179 | "pages": 544, 180 | "name": "The Canterbury Tales", 181 | "article": 1450 182 | }, 183 | { 184 | "brand": "Anton Chekhov", 185 | "country": "Russia", 186 | "imageLink": "images/stories-of-anton-chekhov.jpg", 187 | "language": "Russian", 188 | "link": "https://en.wikipedia.org/wiki/List_of_short_stories_by_Anton_Chekhov\n", 189 | "pages": 194, 190 | "name": "Stories", 191 | "article": 1886 192 | }, 193 | { 194 | "brand": "Joseph Conrad", 195 | "country": "United Kingdom", 196 | "imageLink": "images/nostromo.jpg", 197 | "language": "English", 198 | "link": "https://en.wikipedia.org/wiki/Nostromo\n", 199 | "pages": 320, 200 | "name": "Nostromo", 201 | "article": 1904 202 | }, 203 | { 204 | "brand": "Charles Dickens", 205 | "country": "United Kingdom", 206 | "imageLink": "images/great-expectations.jpg", 207 | "language": "English", 208 | "link": "https://en.wikipedia.org/wiki/Great_Expectations\n", 209 | "pages": 194, 210 | "name": "Great Expectations", 211 | "article": 1861 212 | }, 213 | { 214 | "brand": "Denis Diderot", 215 | "country": "France", 216 | "imageLink": "images/jacques-the-fatalist.jpg", 217 | "language": "French", 218 | "link": "https://en.wikipedia.org/wiki/Jacques_the_Fatalist\n", 219 | "pages": 596, 220 | "name": "Jacques the Fatalist", 221 | "article": 1796 222 | }, 223 | { 224 | "brand": "Alfred D\u00f6blin", 225 | "country": "Germany", 226 | "imageLink": "images/berlin-alexanderplatz.jpg", 227 | "language": "German", 228 | "link": "https://en.wikipedia.org/wiki/Berlin_Alexanderplatz\n", 229 | "pages": 600, 230 | "name": "Berlin Alexanderplatz", 231 | "article": 1929 232 | }, 233 | { 234 | "brand": "Fyodor Dostoevsky", 235 | "country": "Russia", 236 | "imageLink": "images/crime-and-punishment.jpg", 237 | "language": "Russian", 238 | "link": "https://en.wikipedia.org/wiki/Crime_and_Punishment\n", 239 | "pages": 551, 240 | "name": "Crime and Punishment", 241 | "article": 1866 242 | }, 243 | { 244 | "brand": "Fyodor Dostoevsky", 245 | "country": "Russia", 246 | "imageLink": "images/the-idiot.jpg", 247 | "language": "Russian", 248 | "link": "https://en.wikipedia.org/wiki/The_Idiot\n", 249 | "pages": 656, 250 | "name": "The Idiot", 251 | "article": 1869 252 | }, 253 | { 254 | "brand": "Fyodor Dostoevsky", 255 | "country": "Russia", 256 | "imageLink": "images/the-possessed.jpg", 257 | "language": "Russian", 258 | "link": "https://en.wikipedia.org/wiki/Demons_(Dostoyevsky_novel)\n", 259 | "pages": 768, 260 | "name": "The Possessed", 261 | "article": 1872 262 | }, 263 | { 264 | "brand": "Fyodor Dostoevsky", 265 | "country": "Russia", 266 | "imageLink": "images/the-brothers-karamazov.jpg", 267 | "language": "Russian", 268 | "link": "https://en.wikipedia.org/wiki/The_Brothers_Karamazov\n", 269 | "pages": 824, 270 | "name": "The Brothers Karamazov", 271 | "article": 1880 272 | }, 273 | { 274 | "brand": "George Eliot", 275 | "country": "United Kingdom", 276 | "imageLink": "images/middlemarch.jpg", 277 | "language": "English", 278 | "link": "https://en.wikipedia.org/wiki/Middlemarch\n", 279 | "pages": 800, 280 | "name": "Middlemarch", 281 | "article": 1871 282 | }, 283 | { 284 | "brand": "Ralph Ellison", 285 | "country": "United States", 286 | "imageLink": "images/invisible-man.jpg", 287 | "language": "English", 288 | "link": "https://en.wikipedia.org/wiki/Invisible_Man\n", 289 | "pages": 581, 290 | "name": "Invisible Man", 291 | "article": 1952 292 | }, 293 | { 294 | "brand": "Euripides", 295 | "country": "Greece", 296 | "imageLink": "images/medea.jpg", 297 | "language": "Greek", 298 | "link": "https://en.wikipedia.org/wiki/Medea_(play)\n", 299 | "pages": 104, 300 | "name": "Medea", 301 | "article": -431 302 | }, 303 | { 304 | "brand": "William Faulkner", 305 | "country": "United States", 306 | "imageLink": "images/absalom-absalom.jpg", 307 | "language": "English", 308 | "link": "https://en.wikipedia.org/wiki/Absalom,_Absalom!\n", 309 | "pages": 313, 310 | "name": "Absalom, Absalom!", 311 | "article": 1936 312 | }, 313 | { 314 | "brand": "William Faulkner", 315 | "country": "United States", 316 | "imageLink": "images/the-sound-and-the-fury.jpg", 317 | "language": "English", 318 | "link": "https://en.wikipedia.org/wiki/The_Sound_and_the_Fury\n", 319 | "pages": 326, 320 | "name": "The Sound and the Fury", 321 | "article": 1929 322 | }, 323 | { 324 | "brand": "Gustave Flaubert", 325 | "country": "France", 326 | "imageLink": "images/madame-bovary.jpg", 327 | "language": "French", 328 | "link": "https://en.wikipedia.org/wiki/Madame_Bovary\n", 329 | "pages": 528, 330 | "name": "Madame Bovary", 331 | "article": 1857 332 | }, 333 | { 334 | "brand": "Gustave Flaubert", 335 | "country": "France", 336 | "imageLink": "images/l-education-sentimentale.jpg", 337 | "language": "French", 338 | "link": "https://en.wikipedia.org/wiki/Sentimental_Education\n", 339 | "pages": 606, 340 | "name": "Sentimental Education", 341 | "article": 1869 342 | }, 343 | { 344 | "brand": "Federico Garc\u00eda Lorca", 345 | "country": "Spain", 346 | "imageLink": "images/gypsy-ballads.jpg", 347 | "language": "Spanish", 348 | "link": "https://en.wikipedia.org/wiki/Gypsy_Ballads\n", 349 | "pages": 218, 350 | "name": "Gypsy Ballads", 351 | "article": 1928 352 | }, 353 | { 354 | "brand": "Gabriel Garc\u00eda M\u00e1rquez", 355 | "country": "Colombia", 356 | "imageLink": "images/one-hundred-years-of-solitude.jpg", 357 | "language": "Spanish", 358 | "link": "https://en.wikipedia.org/wiki/One_Hundred_Years_of_Solitude\n", 359 | "pages": 417, 360 | "name": "One Hundred Years of Solitude", 361 | "article": 1967 362 | }, 363 | { 364 | "brand": "Gabriel Garc\u00eda M\u00e1rquez", 365 | "country": "Colombia", 366 | "imageLink": "images/love-in-the-time-of-cholera.jpg", 367 | "language": "Spanish", 368 | "link": "https://en.wikipedia.org/wiki/Love_in_the_Time_of_Cholera\n", 369 | "pages": 368, 370 | "name": "Love in the Time of Cholera", 371 | "article": 1985 372 | }, 373 | { 374 | "brand": "Johann Wolfgang von Goethe", 375 | "country": "Saxe-Weimar", 376 | "imageLink": "images/faust.jpg", 377 | "language": "German", 378 | "link": "https://en.wikipedia.org/wiki/Goethe%27s_Faust\n", 379 | "pages": 158, 380 | "name": "Faust", 381 | "article": 1832 382 | }, 383 | { 384 | "brand": "Nikolai Gogol", 385 | "country": "Russia", 386 | "imageLink": "images/dead-souls.jpg", 387 | "language": "Russian", 388 | "link": "https://en.wikipedia.org/wiki/Dead_Souls\n", 389 | "pages": 432, 390 | "name": "Dead Souls", 391 | "article": 1842 392 | }, 393 | { 394 | "brand": "G\u00fcnter Grass", 395 | "country": "Germany", 396 | "imageLink": "images/the-tin-drum.jpg", 397 | "language": "German", 398 | "link": "https://en.wikipedia.org/wiki/The_Tin_Drum\n", 399 | "pages": 600, 400 | "name": "The Tin Drum", 401 | "article": 1959 402 | }, 403 | { 404 | "brand": "Jo\u00e3o Guimar\u00e3es Rosa", 405 | "country": "Brazil", 406 | "imageLink": "images/the-devil-to-pay-in-the-backlands.jpg", 407 | "language": "Portuguese", 408 | "link": "https://en.wikipedia.org/wiki/The_Devil_to_Pay_in_the_Backlands\n", 409 | "pages": 494, 410 | "name": "The Devil to Pay in the Backlands", 411 | "article": 1956 412 | }, 413 | { 414 | "brand": "Knut Hamsun", 415 | "country": "Norway", 416 | "imageLink": "images/hunger.jpg", 417 | "language": "Norwegian", 418 | "link": "https://en.wikipedia.org/wiki/Hunger_(Hamsun_novel)\n", 419 | "pages": 176, 420 | "name": "Hunger", 421 | "article": 1890 422 | }, 423 | { 424 | "brand": "Ernest Hemingway", 425 | "country": "United States", 426 | "imageLink": "images/the-old-man-and-the-sea.jpg", 427 | "language": "English", 428 | "link": "https://en.wikipedia.org/wiki/The_Old_Man_and_the_Sea\n", 429 | "pages": 128, 430 | "name": "The Old Man and the Sea", 431 | "article": 1952 432 | }, 433 | { 434 | "brand": "Homer", 435 | "country": "Greece", 436 | "imageLink": "images/the-iliad-of-homer.jpg", 437 | "language": "Greek", 438 | "link": "https://en.wikipedia.org/wiki/Iliad\n", 439 | "pages": 608, 440 | "name": "Iliad", 441 | "article": -735 442 | }, 443 | { 444 | "brand": "Homer", 445 | "country": "Greece", 446 | "imageLink": "images/the-odyssey-of-homer.jpg", 447 | "language": "Greek", 448 | "link": "https://en.wikipedia.org/wiki/Odyssey\n", 449 | "pages": 374, 450 | "name": "Odyssey", 451 | "article": -800 452 | }, 453 | { 454 | "brand": "Henrik Ibsen", 455 | "country": "Norway", 456 | "imageLink": "images/a-Dolls-house.jpg", 457 | "language": "Norwegian", 458 | "link": "https://en.wikipedia.org/wiki/A_Doll%27s_House\n", 459 | "pages": 68, 460 | "name": "A Doll's House", 461 | "article": 1879 462 | }, 463 | { 464 | "brand": "James Joyce", 465 | "country": "Irish Free State", 466 | "imageLink": "images/ulysses.jpg", 467 | "language": "English", 468 | "link": "https://en.wikipedia.org/wiki/Ulysses_(novel)\n", 469 | "pages": 228, 470 | "name": "Ulysses", 471 | "article": 1922 472 | }, 473 | { 474 | "brand": "Franz Kafka", 475 | "country": "Czechoslovakia", 476 | "imageLink": "images/stories-of-franz-kafka.jpg", 477 | "language": "German", 478 | "link": "https://en.wikipedia.org/wiki/Franz_Kafka_bibliography#Short_stories\n", 479 | "pages": 488, 480 | "name": "Stories", 481 | "article": 1924 482 | }, 483 | { 484 | "brand": "Franz Kafka", 485 | "country": "Czechoslovakia", 486 | "imageLink": "images/the-trial.jpg", 487 | "language": "German", 488 | "link": "https://en.wikipedia.org/wiki/The_Trial\n", 489 | "pages": 160, 490 | "name": "The Trial", 491 | "article": 1925 492 | }, 493 | { 494 | "brand": "Franz Kafka", 495 | "country": "Czechoslovakia", 496 | "imageLink": "images/the-castle.jpg", 497 | "language": "German", 498 | "link": "https://en.wikipedia.org/wiki/The_Castle_(novel)\n", 499 | "pages": 352, 500 | "name": "The Castle", 501 | "article": 1926 502 | }, 503 | { 504 | "brand": "K\u0101lid\u0101sa", 505 | "country": "India", 506 | "imageLink": "images/the-recognition-of-shakuntala.jpg", 507 | "language": "Sanskrit", 508 | "link": "https://en.wikipedia.org/wiki/Abhij%C3%B1%C4%81na%C5%9B%C4%81kuntalam\n", 509 | "pages": 147, 510 | "name": "The recognition of Shakuntala", 511 | "article": 150 512 | }, 513 | { 514 | "brand": "Yasunari Kawabata", 515 | "country": "Japan", 516 | "imageLink": "images/the-sound-of-the-mountain.jpg", 517 | "language": "Japanese", 518 | "link": "https://en.wikipedia.org/wiki/The_Sound_of_the_Mountain\n", 519 | "pages": 288, 520 | "name": "The Sound of the Mountain", 521 | "article": 1954 522 | }, 523 | { 524 | "brand": "Nikos Kazantzakis", 525 | "country": "Greece", 526 | "imageLink": "images/zorba-the-greek.jpg", 527 | "language": "Greek", 528 | "link": "https://en.wikipedia.org/wiki/Zorba_the_Greek\n", 529 | "pages": 368, 530 | "name": "Zorba the Greek", 531 | "article": 1946 532 | }, 533 | { 534 | "brand": "D. H. Lawrence", 535 | "country": "United Kingdom", 536 | "imageLink": "images/sons-and-lovers.jpg", 537 | "language": "English", 538 | "link": "https://en.wikipedia.org/wiki/Sons_and_Lovers\n", 539 | "pages": 432, 540 | "name": "Sons and Lovers", 541 | "article": 1913 542 | }, 543 | { 544 | "brand": "Halld\u00f3r Laxness", 545 | "country": "Iceland", 546 | "imageLink": "images/independent-people.jpg", 547 | "language": "Icelandic", 548 | "link": "https://en.wikipedia.org/wiki/Independent_People\n", 549 | "pages": 470, 550 | "name": "Independent People", 551 | "article": 1934 552 | }, 553 | { 554 | "brand": "Giacomo Leopardi", 555 | "country": "Italy", 556 | "imageLink": "images/poems-giacomo-leopardi.jpg", 557 | "language": "Italian", 558 | "link": "\n", 559 | "pages": 184, 560 | "name": "Poems", 561 | "article": 1818 562 | }, 563 | { 564 | "brand": "Doris Lessing", 565 | "country": "United Kingdom", 566 | "imageLink": "images/the-golden-notebook.jpg", 567 | "language": "English", 568 | "link": "https://en.wikipedia.org/wiki/The_Golden_Notebook\n", 569 | "pages": 688, 570 | "name": "The Golden Notebook", 571 | "article": 1962 572 | }, 573 | { 574 | "brand": "Astrid Lindgren", 575 | "country": "Sweden", 576 | "imageLink": "images/pippi-longstocking.jpg", 577 | "language": "Swedish", 578 | "link": "https://en.wikipedia.org/wiki/Pippi_Longstocking\n", 579 | "pages": 160, 580 | "name": "Pippi Longstocking", 581 | "article": 1945 582 | }, 583 | { 584 | "brand": "Lu Xun", 585 | "country": "China", 586 | "imageLink": "images/diary-of-a-madman.jpg", 587 | "language": "Chinese", 588 | "link": "https://en.wikipedia.org/wiki/A_Madman%27s_Diary\n", 589 | "pages": 389, 590 | "name": "Diary of a Madman", 591 | "article": 1918 592 | }, 593 | { 594 | "brand": "Naguib Mahfouz", 595 | "country": "Egypt", 596 | "imageLink": "images/children-of-gebelawi.jpg", 597 | "language": "Arabic", 598 | "link": "https://en.wikipedia.org/wiki/Children_of_Gebelawi\n", 599 | "pages": 355, 600 | "name": "Children of Gebelawi", 601 | "article": 1959 602 | }, 603 | { 604 | "brand": "Thomas Mann", 605 | "country": "Germany", 606 | "imageLink": "images/buddenbrooks.jpg", 607 | "language": "German", 608 | "link": "https://en.wikipedia.org/wiki/Buddenbrooks\n", 609 | "pages": 736, 610 | "name": "Buddenbrooks", 611 | "article": 1901 612 | }, 613 | { 614 | "brand": "Thomas Mann", 615 | "country": "Germany", 616 | "imageLink": "images/the-magic-mountain.jpg", 617 | "language": "German", 618 | "link": "https://en.wikipedia.org/wiki/The_Magic_Mountain\n", 619 | "pages": 720, 620 | "name": "The Magic Mountain", 621 | "article": 1924 622 | }, 623 | { 624 | "brand": "Herman Melville", 625 | "country": "United States", 626 | "imageLink": "images/moby-dick.jpg", 627 | "language": "English", 628 | "link": "https://en.wikipedia.org/wiki/Moby-Dick\n", 629 | "pages": 378, 630 | "name": "Moby Dick", 631 | "article": 1851 632 | }, 633 | { 634 | "brand": "Michel de Montaigne", 635 | "country": "France", 636 | "imageLink": "images/essais.jpg", 637 | "language": "French", 638 | "link": "https://en.wikipedia.org/wiki/Essays_(Montaigne)\n", 639 | "pages": 404, 640 | "name": "Essays", 641 | "article": 1595 642 | }, 643 | { 644 | "brand": "Elsa Morante", 645 | "country": "Italy", 646 | "imageLink": "images/history.jpg", 647 | "language": "Italian", 648 | "link": "https://en.wikipedia.org/wiki/History_(novel)\n", 649 | "pages": 600, 650 | "name": "History", 651 | "article": 1974 652 | }, 653 | { 654 | "brand": "Toni Morrison", 655 | "country": "United States", 656 | "imageLink": "images/beloved.jpg", 657 | "language": "English", 658 | "link": "https://en.wikipedia.org/wiki/Beloved_(novel)\n", 659 | "pages": 321, 660 | "name": "Beloved", 661 | "article": 1987 662 | }, 663 | { 664 | "brand": "Murasaki Shikibu", 665 | "country": "Japan", 666 | "imageLink": "images/the-tale-of-genji.jpg", 667 | "language": "Japanese", 668 | "link": "https://en.wikipedia.org/wiki/The_Tale_of_Genji\n", 669 | "pages": 1360, 670 | "name": "The Tale of Genji", 671 | "article": 1006 672 | }, 673 | { 674 | "brand": "Robert Musil", 675 | "country": "Austria", 676 | "imageLink": "images/the-man-without-qualities.jpg", 677 | "language": "German", 678 | "link": "https://en.wikipedia.org/wiki/The_Man_Without_Qualities\n", 679 | "pages": 365, 680 | "name": "The Man Without Qualities", 681 | "article": 1931 682 | }, 683 | { 684 | "brand": "Vladimir Nabokov", 685 | "country": "Russia/United States", 686 | "imageLink": "images/lolita.jpg", 687 | "language": "English", 688 | "link": "https://en.wikipedia.org/wiki/Lolita\n", 689 | "pages": 317, 690 | "name": "Lolita", 691 | "article": 1955 692 | }, 693 | { 694 | "brand": "George Orwell", 695 | "country": "United Kingdom", 696 | "imageLink": "images/nineteen-eighty-four.jpg", 697 | "language": "English", 698 | "link": "https://en.wikipedia.org/wiki/Nineteen_Eighty-Four\n", 699 | "pages": 272, 700 | "name": "Nineteen Eighty-Four", 701 | "article": 1949 702 | }, 703 | { 704 | "brand": "Ovid", 705 | "country": "Roman Empire", 706 | "imageLink": "images/the-metamorphoses-of-ovid.jpg", 707 | "language": "Classical Latin", 708 | "link": "https://en.wikipedia.org/wiki/Metamorphoses\n", 709 | "pages": 576, 710 | "name": "Metamorphoses", 711 | "article": 100 712 | }, 713 | { 714 | "brand": "Fernando Pessoa", 715 | "country": "Portugal", 716 | "imageLink": "images/the-book-of-disquiet.jpg", 717 | "language": "Portuguese", 718 | "link": "https://en.wikipedia.org/wiki/The_Book_of_Disquiet\n", 719 | "pages": 272, 720 | "name": "The Book of Disquiet", 721 | "article": 1928 722 | }, 723 | { 724 | "brand": "Edgar Allan Poe", 725 | "country": "United States", 726 | "imageLink": "images/tales-and-poems-of-edgar-allan-poe.jpg", 727 | "language": "English", 728 | "link": "https://en.wikipedia.org/wiki/Edgar_Allan_Poe_bibliography#Tales\n", 729 | "pages": 842, 730 | "name": "Tales", 731 | "article": 1950 732 | }, 733 | { 734 | "brand": "Marcel Proust", 735 | "country": "France", 736 | "imageLink": "images/a-la-recherche-du-temps-perdu.jpg", 737 | "language": "French", 738 | "link": "https://en.wikipedia.org/wiki/In_Search_of_Lost_Time\n", 739 | "pages": 2408, 740 | "name": "In Search of Lost Time", 741 | "article": 1920 742 | }, 743 | { 744 | "brand": "Fran\u00e7ois Rabelais", 745 | "country": "France", 746 | "imageLink": "images/gargantua-and-pantagruel.jpg", 747 | "language": "French", 748 | "link": "https://en.wikipedia.org/wiki/Gargantua_and_Pantagruel\n", 749 | "pages": 623, 750 | "name": "Gargantua and Pantagruel", 751 | "article": 1533 752 | }, 753 | { 754 | "brand": "Juan Rulfo", 755 | "country": "Mexico", 756 | "imageLink": "images/pedro-paramo.jpg", 757 | "language": "Spanish", 758 | "link": "https://en.wikipedia.org/wiki/Pedro_P%C3%A1ramo\n", 759 | "pages": 124, 760 | "name": "Pedro P\u00e1ramo", 761 | "article": 1955 762 | }, 763 | { 764 | "brand": "Rumi", 765 | "country": "Sultanate of Rum", 766 | "imageLink": "images/the-masnavi.jpg", 767 | "language": "Persian", 768 | "link": "https://en.wikipedia.org/wiki/Masnavi\n", 769 | "pages": 438, 770 | "name": "The Masnavi", 771 | "article": 1236 772 | }, 773 | { 774 | "brand": "Salman Rushdie", 775 | "country": "United Kingdom, India", 776 | "imageLink": "images/midnights-children.jpg", 777 | "language": "English", 778 | "link": "https://en.wikipedia.org/wiki/Midnight%27s_Children\n", 779 | "pages": 536, 780 | "name": "Midnight's Children", 781 | "article": 1981 782 | }, 783 | { 784 | "brand": "Saadi", 785 | "country": "Persia, Persian Empire", 786 | "imageLink": "images/bostan.jpg", 787 | "language": "Persian", 788 | "link": "https://en.wikipedia.org/wiki/Bustan_(book)\n", 789 | "pages": 298, 790 | "name": "Bostan", 791 | "article": 1257 792 | }, 793 | { 794 | "brand": "Tayeb Salih", 795 | "country": "Sudan", 796 | "imageLink": "images/season-of-migration-to-the-north.jpg", 797 | "language": "Arabic", 798 | "link": "https://en.wikipedia.org/wiki/Season_of_Migration_to_the_North\n", 799 | "pages": 139, 800 | "name": "Season of Migration to the North", 801 | "article": 1966 802 | }, 803 | { 804 | "brand": "Jos\u00e9 Saramago", 805 | "country": "Portugal", 806 | "imageLink": "images/blindness.jpg", 807 | "language": "Portuguese", 808 | "link": "https://en.wikipedia.org/wiki/Blindness_(novel)\n", 809 | "pages": 352, 810 | "name": "Blindness", 811 | "article": 1995 812 | }, 813 | { 814 | "brand": "William Shakespeare", 815 | "country": "England", 816 | "imageLink": "images/hamlet.jpg", 817 | "language": "English", 818 | "link": "https://en.wikipedia.org/wiki/Hamlet\n", 819 | "pages": 432, 820 | "name": "Hamlet", 821 | "article": 1603 822 | }, 823 | { 824 | "brand": "William Shakespeare", 825 | "country": "England", 826 | "imageLink": "images/king-lear.jpg", 827 | "language": "English", 828 | "link": "https://en.wikipedia.org/wiki/King_Lear\n", 829 | "pages": 384, 830 | "name": "King Lear", 831 | "article": 1608 832 | }, 833 | { 834 | "brand": "William Shakespeare", 835 | "country": "England", 836 | "imageLink": "images/othello.jpg", 837 | "language": "English", 838 | "link": "https://en.wikipedia.org/wiki/Othello\n", 839 | "pages": 314, 840 | "name": "Othello", 841 | "article": 1609 842 | }, 843 | { 844 | "brand": "Sophocles", 845 | "country": "Greece", 846 | "imageLink": "images/oedipus-the-king.jpg", 847 | "language": "Greek", 848 | "link": "https://en.wikipedia.org/wiki/Oedipus_the_King\n", 849 | "pages": 88, 850 | "name": "Oedipus the King", 851 | "article": -430 852 | }, 853 | { 854 | "brand": "Stendhal", 855 | "country": "France", 856 | "imageLink": "images/le-rouge-et-le-noir.jpg", 857 | "language": "French", 858 | "link": "https://en.wikipedia.org/wiki/The_Red_and_the_Black\n", 859 | "pages": 576, 860 | "name": "The Red and the Black", 861 | "article": 1830 862 | }, 863 | { 864 | "brand": "Laurence Sterne", 865 | "country": "England", 866 | "imageLink": "images/the-life-and-opinions-of-tristram-shandy.jpg", 867 | "language": "English", 868 | "link": "https://en.wikipedia.org/wiki/The_Life_and_Opinions_of_Tristram_Shandy,_Gentleman\n", 869 | "pages": 640, 870 | "name": "The Life And Opinions of Tristram Shandy", 871 | "article": 1760 872 | }, 873 | { 874 | "brand": "Italo Svevo", 875 | "country": "Italy", 876 | "imageLink": "images/confessions-of-zeno.jpg", 877 | "language": "Italian", 878 | "link": "https://en.wikipedia.org/wiki/Zeno%27s_Conscience\n", 879 | "pages": 412, 880 | "name": "Confessions of Zeno", 881 | "article": 1923 882 | }, 883 | { 884 | "brand": "Jonathan Swift", 885 | "country": "Ireland", 886 | "imageLink": "images/gullivers-travels.jpg", 887 | "language": "English", 888 | "link": "https://en.wikipedia.org/wiki/Gulliver%27s_Travels\n", 889 | "pages": 178, 890 | "name": "Gulliver's Travels", 891 | "article": 1726 892 | }, 893 | { 894 | "brand": "Leo Tolstoy", 895 | "country": "Russia", 896 | "imageLink": "images/war-and-peace.jpg", 897 | "language": "Russian", 898 | "link": "https://en.wikipedia.org/wiki/War_and_Peace\n", 899 | "pages": 1296, 900 | "name": "War and Peace", 901 | "article": 1867 902 | }, 903 | { 904 | "brand": "Leo Tolstoy", 905 | "country": "Russia", 906 | "imageLink": "images/anna-karenina.jpg", 907 | "language": "Russian", 908 | "link": "https://en.wikipedia.org/wiki/Anna_Karenina\n", 909 | "pages": 864, 910 | "name": "Anna Karenina", 911 | "article": 1877 912 | }, 913 | { 914 | "brand": "Leo Tolstoy", 915 | "country": "Russia", 916 | "imageLink": "images/the-death-of-ivan-ilyich.jpg", 917 | "language": "Russian", 918 | "link": "https://en.wikipedia.org/wiki/The_Death_of_Ivan_Ilyich\n", 919 | "pages": 92, 920 | "name": "The Death of Ivan Ilyich", 921 | "article": 1886 922 | }, 923 | { 924 | "brand": "Mark Twain", 925 | "country": "United States", 926 | "imageLink": "images/the-adventures-of-huckleberry-finn.jpg", 927 | "language": "English", 928 | "link": "https://en.wikipedia.org/wiki/Adventures_of_Huckleberry_Finn\n", 929 | "pages": 224, 930 | "name": "The Adventures of Huckleberry Finn", 931 | "article": 1884 932 | }, 933 | { 934 | "brand": "Valmiki", 935 | "country": "India", 936 | "imageLink": "images/ramayana.jpg", 937 | "language": "Sanskrit", 938 | "link": "https://en.wikipedia.org/wiki/Ramayana\n", 939 | "pages": 152, 940 | "name": "Ramayana", 941 | "article": -450 942 | }, 943 | { 944 | "brand": "Virgil", 945 | "country": "Roman Empire", 946 | "imageLink": "images/the-aeneid.jpg", 947 | "language": "Classical Latin", 948 | "link": "https://en.wikipedia.org/wiki/Aeneid\n", 949 | "pages": 442, 950 | "name": "The Aeneid", 951 | "article": -23 952 | }, 953 | { 954 | "brand": "Vyasa", 955 | "country": "India", 956 | "imageLink": "images/the-mahab-harata.jpg", 957 | "language": "Sanskrit", 958 | "link": "https://en.wikipedia.org/wiki/Mahabharata\n", 959 | "pages": 276, 960 | "name": "Mahabharata", 961 | "article": -700 962 | }, 963 | { 964 | "brand": "Walt Whitman", 965 | "country": "United States", 966 | "imageLink": "images/leaves-of-grass.jpg", 967 | "language": "English", 968 | "link": "https://en.wikipedia.org/wiki/Leaves_of_Grass\n", 969 | "pages": 152, 970 | "name": "Leaves of Grass", 971 | "article": 1855 972 | }, 973 | { 974 | "brand": "Virginia Woolf", 975 | "country": "United Kingdom", 976 | "imageLink": "images/mrs-dalloway.jpg", 977 | "language": "English", 978 | "link": "https://en.wikipedia.org/wiki/Mrs_Dalloway\n", 979 | "pages": 216, 980 | "name": "Mrs Dalloway", 981 | "article": 1925 982 | }, 983 | { 984 | "brand": "Virginia Woolf", 985 | "country": "United Kingdom", 986 | "imageLink": "images/to-the-lighthouse.jpg", 987 | "language": "English", 988 | "link": "https://en.wikipedia.org/wiki/To_the_Lighthouse\n", 989 | "pages": 209, 990 | "name": "To the Lighthouse", 991 | "article": 1927 992 | }, 993 | { 994 | "brand": "Marguerite Yourcenar", 995 | "country": "France/Belgium", 996 | "imageLink": "images/memoirs-of-hadrian.jpg", 997 | "language": "French", 998 | "link": "https://en.wikipedia.org/wiki/Memoirs_of_Hadrian\n", 999 | "pages": 408, 1000 | "name": "Memoirs of Hadrian", 1001 | "article": 1951 1002 | } 1003 | ] 1004 | } --------------------------------------------------------------------------------