翻譯| 《 JavaScript無處不在》第5章 數據庫(^_^)

翻譯| 《 JavaScript無處不在》第5章數據庫(^_^) 前端

寫在最前面

你們好呀,我是毛小悠,是一位前端開發工程師。正在翻譯一本英文技術書籍。mongodb

爲了提升你們的閱讀體驗,對語句的結構和內容略有調整。若是發現本文中有存在瑕疵的地方,或者你有任何意見或者建議,能夠在評論區留言,或者加個人微信:code_maomao,歡迎相互溝通交流學習。shell

(σ゚∀゚)σ..:*☆哎喲不錯哦數據庫

翻譯 | 《JavaScript Everywhere》第5章 數據庫(^_^)express

第5章 數據庫

當我仍是個孩子時,我癡迷地收集各種運動卡。收集卡片的過程當中很大一部分時間是在整理卡片。我將明星球員放在一個盒子裏,有一整個盒子專門放籃球巨星邁克爾·喬丹(Michael Jordan)的紙牌,其他的紙牌則按運動分類,或者按團隊分類。這種組織方法使我可以安全地存儲卡牌,並在任何給定時間內輕鬆找到我要找的卡。我自我感受所知甚少,可是像這樣的存儲系統實際上就等同於數據庫。數據庫的核心是容許咱們存儲信息並在之後檢索它。npm

剛開始進行Web開發時,我發現數據庫使人生畏。我看到不少有關運行數據庫和輸入晦澀的SQL命令的說明,這感受就像是一個附加的抽象概念,我沒法搞定。值得慶幸的是,我最終跨越了障礙,而且再也不被SQL表聯接所嚇倒,所以,若是你仍在躑躅不前,我但願你知道你是能夠瀏覽數據庫的世界的。api

在本書中,咱們將使用MongoDB做爲咱們的首選數據庫。我之因此選擇Mongo,是由於它是Node.js生態系統中的一種流行選擇,而且是適合任何人入門的出色數據庫。Mongo將咱們的數據存儲在相似於JavaScript對象的「文檔」中。這意味着咱們將可以以任何JavaScript開發人員熟悉的格式編寫和檢索信息。可是,若是你擁有一個很是喜歡的數據庫(例如PostgreSQL),那麼本書中涉及的主題只需作不多的工做便可轉移到任何類型的系統上。數組

在使用Mongo以前,咱們須要確保MongoDB服務器在本地運行。這是整個開發過程當中必需的。爲此,請按照第1章中有關係統的說明進行操做。瀏覽器

MongoDB入門

運行Mongo,讓咱們探索如何使用Mongo Shell從終端直接與Mongo交互。安全

首先經過鍵入如下內容打開MongoDB shell

mongo命令:

$ mongo

運行此命令後,你應該看到有關MongoDB Shell本地服務器鏈接的信息,以及打印到終端上的一些其餘信息。如今,咱們能夠從終端應用程序中直接與MongoDB進行交互。咱們可使用使用命令建立一個數據庫。

讓咱們建立一個名爲learning的數據庫:

$ use learning

在本章開頭介紹的卡片收藏中,我將卡片整理在不一樣的盒子中。MongoDB帶來了相同的概念,稱爲集合。

集合是咱們將類似文檔組合在一塊兒的方式。例如,博客應用程序可能有一個文章集合,一個用戶集合和第三個評論集合。若是將集合與JavaScript對象進行比較,它將是一個頂級對象,而文檔是其中的單個對象。咱們能夠像這樣將它可視化:

collection: {
  document: {},
  document: {},
  document: {}.
  ...
}

掌握了這些信息後,讓咱們在學習數據庫的集合中建立一個文檔。咱們將建立一個披薩集合,在其中存儲披薩類型的文檔。在MongoDB shell中輸入如下內容:

$ db.pizza.save({ type: "Cheese" })

若是成功,咱們應該看到一個返回的結果:

WriteResult({ "nInserted" : 1 })

咱們還能夠一次將多個條目寫入數據庫:

$ db.pizza.save([{type: "Veggie"}, {type: "Olive"}])

如今咱們已經向數據庫中寫入了一些文檔,讓咱們對其進行檢索。爲此,咱們將使用MongoDBfind方法。要查看集合中的全部文檔,請運行查找帶有空參數的命令:

$ db.pizza.find()

如今,咱們應該在數據庫中看到全部三個條目。除了存儲數據外,MongoDB還自動爲每一個條目分配一個惟一的ID。結果應以下所示:

{ "_id" : ObjectId("5c7528b223ab40938c7dc536"), "type" : "Cheese" }
{ "_id" : ObjectId("5c7529fa23ab40938c7dc53e"), "type" : "Veggie" }
{ "_id" : ObjectId("5c7529fa23ab40938c7dc53f"), "type" : "Olive" }

咱們還能夠經過屬性值以及Mongo分配的ID查找單個文檔:

$ db.pizza.find({ type: "Cheese" })
$ db.pizza.find({ _id: ObjectId("A DOCUMENT ID HERE") })

咱們不只但願可以找到文檔,並且可以對其進行更新也頗有用。咱們可使用Mongoupdate方法來作到這一點,該方法接受要更改的文檔的第一個參數和第二個參數。讓咱們將蔬菜比薩更新爲蘑菇比薩:

$ db.pizza.update({ type: "Veggie" }, { type: "Mushroom" })

如今,若是咱們運行db.pizza.find(),咱們應該看到你的文檔已經更新:

{ "_id" : ObjectId("5c7528b223ab40938c7dc536"), "type" : "Cheese" }
{ "_id" : ObjectId("5c7529fa23ab40938c7dc53e"), "type" : "Mushroom" }
{ "_id" : ObjectId("5c7529fa23ab40938c7dc53f"), "type" : "Olive" }

與更新文檔同樣,咱們也可使用Mongoremove方法刪除一個文檔。讓咱們從數據庫中刪除蘑菇披薩:

$ db.pizza.remove({ type: "Mushroom" })

如今,若是咱們執行數據庫:db.pizza.find() 查詢,咱們只會在集合中看到兩個條目。

若是咱們決定再也不但願包含任何數據,則能夠在沒有空對象參數的狀況下運行remove方法,這將清除整個集合:

$ db.pizza.remove({})

如今,咱們已經成功地使用MongoDB Shell建立數據庫,將文檔添加到集合中,更新這些文檔並刪除它們。當咱們將數據庫集成到項目中時,這些基本的數據庫操做將提供堅實的基礎。在開發中,咱們還可使用MongoDB Shell訪問數據庫。這對於調試、手動刪除或更新條目等任務頗有幫助。

將MongoDB鏈接到咱們的應用程序

如今,你已經從shell中學習了一些有關使用MongoDB的知識,讓咱們將其鏈接到咱們的API應用程序。爲此,咱們將使用Mongoose對象文檔映射器(ODM)。Mongoose是一個庫,它經過使用基於模式的建模解決方案來減小和簡化樣式代碼,從而簡化了在Node.js應用程序中使用MongoDB的工做量。是的,你沒看錯-另外一種模式!如你所見,一旦定義了數據庫模式,經過Mongoose使用MongoDB的方式與咱們在Mongo Shell中編寫的命令的類型相似。

咱們首先須要更新在咱們本地數據庫URL的.env文件。這將使咱們可以在咱們正在使用的任何環境(例如本地開發和生產)中設置數據庫URL

本地MongoDB服務器的默認URLmongodb://localhost:27017,咱們將在其中添加數據庫名稱。所以,在咱們.env文件,咱們將使用Mongo數據庫實例的URL設置一個DB_HOST變量,以下所示:

DB_HOST=mongodb://localhost:27017/notedly

在咱們的應用程序中使用數據庫的下一步是鏈接到該數據庫。讓咱們寫一些代碼,在啓動時將咱們的應用程序鏈接到咱們的數據庫。爲此,咱們將首先在src目錄中建立一個名爲db.js的新文件。在db.js中,咱們將編寫數據庫鏈接代碼。咱們還將包括一個關閉數據庫鏈接的功能,這將對測試應用程序頗有用。

src/db.js中,輸入如下內容:

// Require the mongoose library
const mongoose = require('mongoose');

module.exports = {
  connect: DB_HOST => {
    // Use the Mongo driver's updated URL string parser
    mongoose.set('useNewUrlParser', true);
    // Use findOneAndUpdate() in place of findAndModify()
    mongoose.set('useFindAndModify', false);
    // Use createIndex() in place of ensureIndex()
    mongoose.set('useCreateIndex', true);
    // Use the new server discovery and monitoring engine
    mongoose.set('useUnifiedTopology', true);
    // Connect to the DB
    mongoose.connect(DB_HOST);
    // Log an error if we fail to connect
    mongoose.connection.on('error', err => {
      console.error(err);
      console.log(
        'MongoDB connection error. Please make sure MongoDB is running.'
      );
      process.exit();
    });
  },

  close: () => {
    mongoose.connection.close();
  }
};

如今,咱們將更新src/index.js來調用此鏈接。爲此,咱們將首先導入.env配置以及db.js文件。在導入中,在文件頂部,添加如下導入:

require('dotenv').config();
const db = require('./db');

我喜歡在env文件中定義DB_HOST值做爲一個變量。直接在下面的端口變量定義中添加此變量:

const DB_HOST = process.env.DB_HOST;

而後,經過將如下內容添加到src/index.js文件中,能夠調用咱們的鏈接:

db.connect(DB_HOST);

src/index.js文件如今以下:

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
require('dotenv').config();

const db = require('./db');

// Run the server on a port specified in our .env file or port 4000
const port = process.env.PORT || 4000;
// Store the DB_HOST value as a variable
const DB_HOST = process.env.DB_HOST;

let notes = [
  {
    id: '1',
    content: 'This is a note',
    author: 'Adam Scott'
  },
  {
    id: '2',
    content: 'This is another note',
    author: 'Harlow Everly'
  },
  {
    id: '3',
    content: 'Oh hey look, another note!',
    author: 'Riley Harrison'
  }
];

// Construct a schema, using GraphQL's schema language
const typeDefs = gql`
  type Note {
    id: ID
    content: String
    author: String
  }

  type Query {
    hello: String
    notes: [Note]
    note(id: ID): Note
  }

  type Mutation {
    newNote(content: String!): Note
  }
`;

// Provide resolver functions for our schema fields
const resolvers = {
  Query: {
    hello: () => 'Hello world!',
    notes: () => notes,
    note: (parent, args) => {
      return notes.find(note => note.id === args.id);
    }
  },
  Mutation: {
    newNote: (parent, args) => {
      let noteValue = {
        id: notes.length + 1,
        content: args.content,
        author: 'Adam Scott'
      };
      notes.push(noteValue);
      return noteValue;
    }
  }
};

const app = express();

// Connect to the database
db.connect(DB_HOST);

// Apollo Server setup
const server = new ApolloServer({ typeDefs, resolvers });

// Apply the Apollo GraphQL middleware and set the path to /api
server.applyMiddleware({ app, path: '/api' });

app.listen({ port }, () =>
  console.log(
    `GraphQL Server running at http://localhost:${port}${server.graphqlPath}`
  )
);

儘管實際功能沒有更改,可是若是你運行npm run dev,則應用程序應該成功鏈接到數據庫而且運行沒有錯誤。

從咱們的應用程序讀取和寫入數據

如今咱們能夠鏈接到數據庫了,讓咱們編寫從應用程序內部讀取數據和向其寫入數據所需的代碼。Mongoose容許咱們定義如何將數據做爲JavaScript對象存儲在數據庫中,而後咱們能夠存儲匹配該模型結構的數據並對其進行操做。考慮到這一點,讓咱們建立咱們的對象,稱爲Mongoose模式。

首先,在咱們的src目錄中建立一個名爲models的文件夾來存放該模式文件。在此文件夾中,建立一個名爲note.js的文件。在src/models/note.js中,咱們將從定義文件的基本設置開始:

// Require the mongoose library
const mongoose = require('mongoose');

// Define the note's database schema
const noteSchema = new mongoose.Schema();

// Define the 'Note' model with the schema
const Note = mongoose.model('Note', noteSchema);
// Export the module
module.exports = Note;

接下來,咱們將在noteSchema變量中定義咱們的模式。與內存數據示例相似,目前,咱們當前的結構中將包括筆記的內容以及表明做者的字符串。咱們還將包括爲筆記添加時間戳的選項,當建立或編輯筆記時,時間戳將自動存儲。咱們將繼續在筆記結構中添加這些功能。

咱們的Mongoose模式的結構以下:

// Define the note's database schema
const noteSchema = new mongoose.Schema(
  {
    content: {
      type: String,
      required: true
    },
    author: {
      type: String,
      required: true
    }
  },
  {
    // Assigns createdAt and updatedAt fields with a Date type
    timestamps: true
  }
);

數據永久性

咱們將在整個開發過程當中更新和更改數據模型,有時會從數據庫中刪除全部數據。所以,我不建議使用此API存儲重要的內容,例如課堂筆記、朋友的生日列表或前往你最喜歡的披薩店的地址導航信息。

如今,咱們的總體src/models/note.js文件應以下所示:

// Require the mongoose library
const mongoose = require('mongoose');

// Define the note's database schema
const noteSchema = new mongoose.Schema(
  {
    content: {
      type: String,
      required: true
    },
    author: {
      type: String,
      required: true
    }
  },
  {
    // Assigns createdAt and updatedAt fields with a Date type
    timestamps: true
  }
);

// Define the 'Note' model with the schema
const Note = mongoose.model('Note', noteSchema);
// Export the module
module.exports = Note;

爲了簡化將模型導入Apollo Server Express應用程序的過程,咱們將向index.js文件添加到src/models目錄中。這會將咱們的模型合併到一個JavaScript模塊中。儘管這不是嚴格必要的,但隨着應用程序和數據庫模型的增加,我認爲這是一個很好的策略。在src/models/index.js中,咱們將導入筆記模型並將其添加到要導出的模塊對象中:

const Note = require('./note');

const models = {
  Note
};

module.exports = models;

如今,經過將模塊導入到src/index.js文件中,咱們能夠將數據庫模塊合併到Apollo Server Express應用程序代碼中:

const models = require('./models');

導入數據庫模塊代碼後,咱們可使解析器實現保存和讀取數據庫的需求,而不是經過存放在內存中的變量。爲此,咱們將重寫notes查詢,用來經過使用從MongoDB數據庫中提取筆記:

notes: async () => {
  return await models.Note.find();
},

在服務器運行後,咱們能夠在瀏覽器中訪問GraphQL Playground並運行筆記查詢:

query {
  notes {
    content
    id
    author
  }
}

預期結果將是一個空數組,由於咱們還沒有向數據庫中添加任何數據(圖5-1):

{
  "data": {
    "notes": []
  }
}

img

5-1。筆記查詢。

更新咱們的newNote修改以向咱們的數據庫中添加一個筆記,咱們將使用MongoDB模塊的create方法來接受一個對象。

如今,咱們將繼續對做者的姓名進行編寫:

newNote: async (parent, args) => {
  return await models.Note.create({
   content: args.content,
   author: 'Adam Scott'
  });
}

如今,咱們能夠訪問GraphQL Playground並編寫一個修改,該修改將爲咱們的數據庫添加一個筆記:

mutation {
  newNote (content: "This is a note in our database!") {
  content
  author
  id
  }
}

咱們的修改將返回一個新筆記,其中包含咱們放入變量中的內容,做者的姓名以及MongoDB生成的ID(圖5-2)。

img

5-2。修改會在數據庫中建立新筆記

若是如今從新運行筆記查詢,則應該看到從數據庫中檢索到的筆記!(請參閱圖5-3

img

5-3。咱們的筆記查詢返回數據庫中的數據。

最後一步是使用MongoDB分配給每一個條目的惟一ID重寫note的查詢,用於從數據庫中提取特定的筆記。爲此,咱們將使用MongoosefindbyId方法:

note: async (parent, args) => {
  return await models.Note.findById(args.id);
}

如今,咱們可使用在筆記查詢或newNote修改中看到的惟一ID查詢了,能夠從數據庫中檢索單個筆記。爲此,咱們將編寫一個帶id參數的筆記查詢(圖5-4):

query {
  note(id: "5c7bff794d66461e1e970ed3") {
   id
   content
   author
  }
}

你的筆記編號

上一個示例中使用的ID對於個人本地數據庫是惟一的。確保從你本身的查詢或修改結果中複製一個ID

img

5-4。查詢單個筆記

咱們最終的src/index.js文件將以下所示:

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
require('dotenv').config();

const db = require('./db');
const models = require('./models');

// Run our server on a port specified in our .env file or port 4000
const port = process.env.PORT || 4000;
const DB_HOST = process.env.DB_HOST;

// Construct a schema, using GraphQL's schema language
const typeDefs = gql`
  type Note {
   id: ID
   content: String
   author: String
  }

  type Query {
   hello: String
   notes: [Note]
   note(id: ID): Note
  }

  type Mutation {
   newNote(content: String!): Note
  }
`;

// Provide resolver functions for our schema fields
const resolvers = {
  Query: {
   hello: () => 'Hello world!',
   notes: async () => {
    return await models.Note.find();
   },
   note: async (parent, args) => {
    return await models.Note.findById(args.id);
   }
  },
  Mutation: {
   newNote: async (parent, args) => {
    return await models.Note.create({
     content: args.content,
     author: 'Adam Scott'
    });
   }
  }
};

const app = express();

db.connect(DB_HOST);

// Apollo Server setup
const server = new ApolloServer({ typeDefs, resolvers });

// Apply the Apollo GraphQL middleware and set the path to /api
server.applyMiddleware({ app, path: '/api' });

app.listen({ port }, () =>
  console.log(
   `GraphQL Server running at http://localhost:${port}${server.graphqlPath}`
  )
);

如今,咱們可使用GraphQL API從數據庫讀取和寫入數據!嘗試增長更多的筆記,瀏覽筆記查詢全部的注意事項。並瀏覽單個筆記查詢的信息內容。

結論

在本章中,你學習瞭如何經過咱們的API使用MongoDBMongoose庫。數據庫(例如MongoDB)使咱們可以安全地存儲和檢索應用程序的數據。對象建模庫(例如Mongoose)經過提供用於數據庫查詢和數據驗證的工具來簡化數據庫的工做。在下一章中,咱們將更新API以使數據庫內容具備完整的CRUD(建立,讀取,更新和刪除)功能。

若是有理解不到位的地方,歡迎你們糾錯。若是以爲還能夠,麻煩您點贊收藏或者分享一下,但願能夠幫到更多人。:slightly_smiling_fac

相關文章
相關標籤/搜索